Erstellung eines eigenen Formularfeldes

From Joomla! Documentation

This page is a translated version of the page Creating a custom form field type and the translation is 100% complete.

Other languages:
Deutsch • ‎English • ‎español • ‎français • ‎Nederlands
Joomla! 
3.x
Serie
Copyedit.png
This Article Needs Your Help

This article is tagged because it NEEDS REVIEW. You can help the Joomla! Documentation Wiki by contributing to it.
More pages that need help similar to this one are here. NOTE-If you feel the need is satistified, please remove this notice.

JForm, ein Feature das in Joomla! 2.5 eingeführt wurde, vereinfacht das Erstellen von HTML Formularen (<form>). Formulare die mit JForm erstellt wurden, beinhalten Formularfelder, implementiert als JFormFields. Für jede Art von Feldtyp, den man in einem Formular finden kann, wie ein Text-Feld oder ein Datum-Feld, gibt es ein JFormField. JForm unterstützt eine große Auswahl von Standardfeldtypen. Für eine vollständige Liste, siehe Standardformularfeldtypen.

Joomla! 2.5 ermöglicht es Standardfeldtypen zu erweitern oder ein eigenes zu definieren. Zum Beispiel könnte die eigene Komponente Telefonbucheinträge verwalten, was dazu führt, dass man einen Formularfeldtypen erstellen will, der eine Liste an Städten ausgibt. Es gibt mehrere Vorteile beim Erstellen eines eigenen Formfeldtypens:

  • Man kann Standardfeldtypen mit den eigenen Feldtypen in einem JForm-basierten Formular kombinieren.
  • Man kann endlich ein wiederverwendbares Code-Paket einfach überall im Code einsetzen.
  • Erweiterungen, die mit der eigenen Erweiterung zusammenarbeiten, sind in der Lage ein Formularfeld zu erstellen, ohne sich in deren Datenbank oder anderen internen Angelegenheiten einzumischen.

Vorraussetzungen für Formularfeldtyp-Klassen

Ein Formularfeldtyp wird in einer Klasse definiert, welche keine Unterklasse von JFormField sein darf (jedenfalls nicht direkt). Um richtig zu funktionieren, müssen zumindest drei Methoden implementiert sein:

  • public function getLabel()
    Diese Funktion wird aufgerufen um das Label zu erstellen, welches zum Feld gehört und gibt eine HTML-Zeichenkette zurück, die dieses beinhaltet. Seitdem JFormField eine ready-to-use getLabel() Methode implementiert, erstellen eigene Formfeldtypen normalerweise nicht mehr ihre eigene. Denn wenn man sie auslässt, wird diese geerbte Methode benutzt. Das wird auch für mehr Konsistenz und Geschwindigkeit empfohlen, außer man will wirklich das HTML des Labels verändern.
  • public function getInput()
    Hiermit wird das Feld selbst erstellt und gibt eine HTML-Zeichenkette zurück, in der es enthalten ist. Hier passiert überlicherweise das Meiste der Entwicklung. In dem Beispiel mit dem Telefonbuch-Städte-Feld holt diese Funktion eine Liste aller verfügbaren Städte und gibt ein HTML <select> mit den Städten als <option> zurück.
  • public function getValue()
    Diese Funktion gibt den Wert des Feldes zurück. Dieser Wert wird von der Funktion LoadFormData des Models geholt.

Innerhalb des Codes, müssen die Attribute aus dem Feld in der XML-Formulardefinition geholt werden, die vom Benutzer gesetzt wurden. Manche dieser Attribute können über protected Variablen von JFormField erreicht werden. Das Attribut name steht zum Beispiel im Code als $this->name zur Verfügung. Genauso sind label, description, default, multiple und class auch als Attribut von $this vorhanden. Andere Parameter die man möglicherweise definiert hat, sind über das Array $this->element erreichbar. Das Attribut size liegt z.B. in $this->element['size'].

Welche Klasse erweitern?

Um ein Formularfeld in JForm nutzen zu können, muss es eine Unterklasse von JFormField sein. Dennoch muss es kein direkter Ableger der Klasse sein. Man kann auch bereits vorhandene (Standard oder eigene) Formfeldtypen und somit nützlichen Code vererben.

Wenn der eigene Formfeldtyp ziemlich ähnlich mit einem existierenden Typen ist, sollte man ihn als Unterklasse umsetzen. Insbesondere wenn der Formfeldtyp eine Liste ist, bitte von JFormFieldList erben. Die getOptions() Methode kann ganz einfach überschrieben werden, um die anzuzeigende Auswahl zurückzugeben; die getInput() Methode wird diese Auswahl dann zu HTML umwandeln.

Um einen existierenden Typen zu erweitern, z.B. JFormFieldList, lädt man den folgenden Code hinter jimport('joomla.form.formfield');:

jimport('joomla.form.helper');
JFormHelper::loadFieldClass('list');

Wenn der Formfeldtyp keinem existierenden Typen ähnelt, erbt man direkt von JFormField.

Ort der Dateien

  • Standard Formfeldtypen findet man in libraries/joomla/form/fields/. Dort sollen weder eigene Felder abgelegt, noch dieser Pfad im eigenen Code verwendet werden. Aber die Standardtypen sind für gewöhnlich gute Beispiele.
  • Erstellte Feldtypen die zur eigenen Komponente gehören, liegen normalerweise in administrator/components/<Name der Komponente>/models/fields. Man kann diesen Pfad oder einen anderen im Code nutzen:
JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
  • Die XML-Dateien die Formulare definieren, sind normalerweise in administrator/components/<Name der Komponente>/models/forms zu finden. So etwas wie im folgenden Abschnitt kann genutzt werden, um einen Pfad zum eigenen Formular zu setzen:
JForm::addFormPath(JPATH_COMPONENT . '/models/forms');

Namenskonventionen und Grundgerüste

In diesem Abschnitt repräsentiert <KomponentenName> den camel-case Namen der Komponente und <FeldName> den camel-case Namen des Formfeldtypens. Die Klasse des Feldes sollte in administrator/components/<Name der Komponente>/models/fields/<Name des Feldes>.php liegen und so aussehen:

<?php
// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

jimport('joomla.form.formfield');

// The class name must always be the same as the filename (in camel case)
class JFormField<FieldName> extends JFormField {

	//The field class must know its own type through the variable $type.
	protected $type = '<FieldName>';

	public function getLabel() {
		// code that returns HTML that will be shown as the label
	}

	public function getInput() {
		// code that returns HTML that will be shown as the form field
	}
}

Gruppierung eigener Feldtypen

Warnung: Diese Information ist teilweise falsch und benötigt Verbesserung.

Eigene Feldtypen können gruppiert werden, indem ein Unterstrich im Feldnamen benutzt wird. Eine Feld-Klasse mit einem Namen wie "JFormFieldMy_randomField" muss zum Beispiel in administrator/components/<Name der Komponente>/models/fields/my/randomField.php angelegt werden. Man kann einem Formfeldnamen einen Gruppennamen voranstellen, dann kann man einen Unterstrich setzen und dann erst den Namen des Feldes.

Beispiel eines eigenen Feldtypens

Angenommen man arbeitet an einer Komponente namens com_phonebook und man möchte ein Feld definieren, dass Städte beinhaltet. Dann erstellt man die Datei administrator/components/com_phonebook/models/fields/city.php und schreibt so etwas wie das Folgende:

<?php
// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

jimport('joomla.form.formfield');

class JFormFieldCity extends JFormField {
	
	protected $type = 'City';

	// getLabel() left out

	public function getInput() {
		return '<select id="'.$this->id.'" name="'.$this->name.'">'.
		       '<option value="1" >New York</option>'.
		       '<option value="2" >Chicago</option>'.
		       '<option value="3" >San Francisco</option>'.
		       '</select>';
	}
}

Ein eher fortgeschrittener Ansatz ist, die Klasse JFormFieldList zu erweitern. Angenommen man möchte ein Dropdown-Feld erstellen, welches Städte dynamisch aus der Datenbank lädt, basierend auf einer dynamischen Bedingung, kann man es auf die folgende Art und Weise machen:

<?php
// Check to ensure this file is included in Joomla!
defined('_JEXEC') or die('Restricted access');

JFormHelper::loadFieldClass('list');

class JFormFieldCity extends JFormFieldList {

	protected $type = 'City';

	public function getOptions() {
                $app = JFactory::getApplication();
                $country = $app->input->get('country'); //country is the dynamic value which is being used in the view
                $db = JFactory::getDbo();
                $query = $db->getQuery(true);
                $query->select('a.cityname')->from('`#__tablename` AS a')->where('a.country = "'.$country.'" ');
		$rows = $db->setQuery($query)->loadObjectlist();
                foreach($rows as $row){
                    $cities[] = $row->cityname;
                }
                // Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $cities);
                return $options;
	}
}

Das obige Beispiel zeigt eine einfache Abfrage für eine Liste an Städten aus einer Tabelle, in der Städtenamen mit dem entsprechenden Land angezeigt werden. Man kann ein Dropdown-Feld erstellen, dass auf komplexeren Abfragen basiert.

Fallen

Ein eigenes Feld zu laden kann in einem Fatal Error enden, wenn ein Core-Feld mit demselben Feldnamen existiert und das eigene Feld dieses Core-Feld erweitert.

Betrachtet man die Datei testfields/radio.php mit dem Inhalt

<?php

class TestFormFieldRadio extends JFormFieldRadio {}

Der Aufruf von JFormHelper::loadFieldClass('radio') wird einen Fatal Error liefern: Class 'JFormFieldRadio' not found.

Es gibt dafür zwei Gründe.

  1. JLoader kann JFormFieldRadio nicht automatisch laden, weil der Klassenname (JFormField*) nicht mit dem Pfadnamen (joomla/form/fields/* - man beachte den Plural von Feldern) übereinstimmt.
  2. JFormHelper kann JFormFieldRadio nicht automatisch laden, weil eigens erstellte Pfade zuerst gescannt werden und der angeforderte Feldtyp ('radio') aufgelöst wird, bevor die Core-Klassen erreicht wurden.

Lösung

Das Core-Feld direkt verlangen:

<?php
require_once JPATH_LIBRARIES . '/joomla/form/fields/radio.php';

class TestFormFieldRadio extends JFormFieldRadio {}

und JFormHelper::loadFieldClass richtig mit 'test.radio' anstatt 'radio' benutzen.

Benutzen des eigenen Feldtypens

Verbunden mit einem Formular

Um den Feldtypen City zu benutzen, ist es nötig die XML-Datei zu aktualisieren, welche das Formularfeld beinhaltet. Man öffnet die XML-Datei in dem Ordner administrator/components/com_phonebook/models/forms und fügt das Feld überlicherweise hinzu:

<field name="title" type="City" label="JGLOBAL_TITLE"
	description="JFIELD_TITLE_DESC"
	required="true" />

Der Attributname ist cAsE-sEnSiTiVe.

Zuzüglich ist es nötig einen Feldpfad zum Eltern-<fieldset> hinzuzufügen.

<fieldset addfieldpath="/administrator/components/<component name>/models/fields">

Nicht verbunden mit einem Formular

Wenn man zum Beispiel ein Feld als Dropdown, in einer Komponente als admin/site Filter, braucht:

//Get custom field
JFormHelper::addFieldPath(JPATH_COMPONENT . '/models/fields');
$cities = JFormHelper::loadFieldType('City', false);
$cityOptions=$cities->getOptions(); // works only if you set your field getOptions on public!!

Überschreiben von getLabel()

Wie bereits in dem Abschnitt Vorraussetzungen für Formularfeldtyp-Klassen erwähnt, definieren eigene Formfeldtypen normalerweise nicht ihre eigene getLabel() Methode. Möchte man ein eigenes Label erstellen, kann man immer noch getLabel() in Anspruch nehmen, was jede Feldtyp-Klasse von JFormField erbt. Zum Beispiel indem man es wie folgt definiert:

public function getLabel() {
     return '<span style="text-decoration: underline;">' . parent::getLabel() . '</span>';
}

Dieser Code unterstreicht Formular-Labels. (Bitte beachten, dass CSS der bevorzugte Weg ist, um Labels zu unterstreichen.)

Wenn man etwas völlig anderes machen möchte, kann man es natürlich auch komplett überschreiben:

public function getLabel() {
	// Initialize variables.
	$label = '';
	$replace = '';

	// Get the label text from the XML element, defaulting to the element name.
	$text = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];

	// Build the class for the label.
	$class = !empty($this->description) ? 'hasTip' : '';
	$class = $this->required == true ? $class.' required' : $class;
		
	// Add replace checkbox
	$replace = '<input type="checkbox" name="update['.$this->name.']" value="1" />';
		
	// Add the opening label tag and main attributes attributes.
	$label .= '<label id="'.$this->id.'-lbl" for="'.$this->id.'" class="'.$class.'"';

	// If a description is specified, use it to build a tooltip.
	if (!empty($this->description)) {
		$label .= ' title="'.htmlspecialchars(trim(JText::_($text), ':').'::' .
				JText::_($this->description), ENT_COMPAT, 'UTF-8').'"';
	}

	// Add the label text and closing tag.
	$label .= '>'.$replace.JText::_($text).'</label>';
	
	return $label; 
}

Dieses Beispiel fügt eine Checkbox innerhalb des Labels ein.