Création d'un type de champ de formulaire personnalisé

From Joomla! Documentation

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

Outdated translations are marked like this.
Other languages:
English • ‎español • ‎français • ‎Nederlands
Joomla! 
3.x
série
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, une fonctionnalité introduite dans Joomla! 2.5, vous permet de créer facilement des formulaires HTML (<form>). Les formulaires créés en utilisant JForm consistent en des champs de formulaire, implémentés en tant que JFormFields. Il existe un JFormField pour chaque différent type de champ que vous trouvez dans un formulaire, comme un champ de type texte, un champ de type date. JForm supporte une large sélection de types de champ standards. Pour une liste complète, consultez les types de champs de formulaire standards.

Joomla! 2.5 permet d'étendre les types de champ standards ou de définir vos propres champs. Par exemple, si votre composant gère les entrées d'un répertoire téléphonique, vous pouvez définir un type de champ de formulaire qui affichera une liste de sélection par ville. Il existe plusieurs avantages à définir un type de champ de formulaire personnalisé :

  • Vous pouvez mixer les types de champ standards avec votre type de champ personnalisé au sein d'un formulaire JForm.
  • Vous obtenez un code réutilisable facilement dans votre code.
  • Les extensions qui collaborent avec votre extension sont à même de créer des champs de formulaire sans ingérer avec vos tables de données et autres éléments internes.

Exigences de classe pour les types de champ de formulaire

Un type de champ de formulaire est défini par une classe qui doit être une sous-classe de JFormField (pas nécessairement directe). Pour fonctionner correctement, la classe doit définir au moins trois méthodes :

  • public function getLabel()
    Cette fonction va être appelée pour créer l'étiquette de votre champ et va retourner une chaîne HTML la contenant. Puisque JFormField définit une implémentation de getLabel() prête à l'emploi, les types de champ de formulaire personnalisés ne définissent pas en général leurs propres getLabel(). Si vous l'omettez, la méthode héritée de création d'étiquette sera utilisée. il est recommandé de laisser de côté la méthode getLabel() pour des raisons de cohérence et de vitesse sauf si vous voulez vraiment modifier le code HTML de l'étiquette.
  • public function getInput()
    Cette fonction va être appelée pour créer le champ lui-même et retourne une chaîne HTML le contenant. C'est également là que la majeure partie du processus intervient. Dans notre exemple de champ d'annuaire téléphonique, cette fonction va permettre de récupérer une liste des villes disponibles et va retourner un HTML <select> avec les villes insérées comme <option>s.
  • public function getValue()
    Cette fonction sera appelée pour obtenir la valeur du champ, La valeur est obtenue depuis la fonction LoadFormData du modèle.

Au sein de votre code, vous aurez à traiter les attributs définis par l'utilisateur du champ dans la définition XML du formulaire. Certains de ces attributs sont accessibles depuis des variables protégeés de JFormField. Par exemple, l'attribut name est disponible dans votre code en tant que $this->name. De même, label, description, default, multiple et class sont également disponibles en tant que propriétés de $this. D'autres paramètres que vous pourriez souhaiter définir sont accessibles via l'array $this->element : l'attribut size sera dans $this->element['size'].

Quelle classe pour la sous-classe ?

Pour qu'un type de champ de formulaire soit utilisable dans JForm, il doit être une sous-classe de JFormField. Cependant, il n'a pas besoin d'être un enfant direct de cette classe : vous pouvez également sous-classé un type de champ de formulaire existant (standard ou personnalisé) et ainsi hériter du code nécessaire.

Si votre type de champ de formulaire est relativement similaire à un type existant, vous devriez sous-classer ce type. Si votre type de champ de formulaire est une liste, veuillez la sous-classer en JFormFieldList. Vous n'avez qu'à substituer la méthode getOptions() pour envoyer les options à afficher ; la méthode getInput() convertira ces options en HTML.

Pour sous-classer un type existant, par exemple JFormFieldList, chargez-le en ajoutant ce qui suit après jimport('joomla.form.formfield');:

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

Si votre type de champ de formulaire diffère de tout type existant, sous-classez JFormField directement.

Emplacement des fichiers

  • Les types de champ de formulaire standards sont situés dans libraries/joomla/form/fields/. Vous ne devez pas stocker vos champs personnalisés à cet endroit, ni utiliser ce chemin dans votre propre code, mais les types standards constituent généralement de bons exemples.
  • Les types de champ personnalisés de votre composant sont généralement situés dans administrator/components/<nom de votre composant>/models/fields. Vous pouvez indiquer ceci ou un autre chemin dans votre code :
JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
  • Les fichiers XML qui définissent les formulaires sont généralement situés dans administrator/components/<nom de votre composant>/models/forms. Utilisez quelque chose comme le fragment de code suivant pour spécifier un chemin pour vos formulaires :
JForm::addFormPath(JPATH_COMPONENT . '/models/forms');

Conventions de nommage et squelette

Dans cette section, <ComponentName> représente le nom de votre composant avec la première lettre de chaque mot en majuscule et <FieldName> représente le nom de votre type de champ de formulaire avec la première lettre de chaque mot en majuscule. La classe de champ doit être placée dans administrator/components/<nom de votre composant>/models/fields/<nom de votre champ>.php, et ressembler à ceci :

<?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
	}
}

Regrouper les types de champ personnalisés

Attention : cette information est partiellement incorrecte et nécessite des améliorations.

Les types de champ personnalisés peuvent être regroupés en utilisant un trait de soulignement (_) dans le nom du champ. Une classe de champ avec un nom tel que, par exemple, "JFormFieldMy_randomField" doit être stocké dans administrator/components/<nom de votre composant>/models/fields/my/randomField.php. Nous pouvons ajouter un préfixe aux noms des champs de formulaire avec un nom de groupe, puis nous mettons un trait de soulignement et enfin un nom de champ.

Un exemple de type de champ personnalisé

Supposons que vous travaillez sur votre composant nommé com_phonebook et que vous souhaitez définir un champ contenant les villes. Créez le fichier administrator/components/com_phonebook/models/fields/city.php et écrivez quelque chose similaire à ceci :

<?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>';
	}
}

Une approche plus avancée est l'extension de la classe JFormFieldList. Supposons que vous souhaitiez créer une liste déroulante des villes de manière dynamique à partir de la base de données de façon dynamique, alors vous pouvez faire cela de la manière suivante :

<?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;
	}
}

L'exemple ci-dessus montre une requête simple permettant d'afficher une liste de villes à partir d'une table où le nom de la ville est relative à son pays respectif. Vous pouvez créer une liste déroulante basée sur des requêtes plus complexes.

Pitfalls

Loading a custom field can result in a Fatal Error, if a core field exists with the same filename, and the custom field extends the core field.

Consider a file testfields/radio.php containing

<?php

class TestFormFieldRadio extends JFormFieldRadio {}

Calling JFormHelper::loadFieldClass('radio') will yield in a Fatal error: Class 'JFormFieldRadio' not found.

Il y deux raisons pour cela.

  1. JLoader cannot autoload JFormFieldRadio, because the class name (JFormField*) does not match the path name (joomla/form/fields/* - notice the plural on fields).
  2. JFormHelper cannot load JFormFieldRadio, because custom paths are scanned first, and the requested field type('radio') gets resolved before the core classes are reached.

Solution

Require the core field file directly:

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

class TestFormFieldRadio extends JFormFieldRadio {}

and use JFormHelper::loadFieldClass properly with 'test.radio' instead of 'radio'.

Utilisation du type de champ personnalisé

Lié à un formulaire

Pour utiliser le type de champ City, il nous faut mettre à jour le fichier XML contenant les champs de formulaire. Ouvrez votre fichier XML situé dans administrator/components/com_phonebook/models/forms et ajoutez le champ comme habituellement :

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

Le nom d'attribut est sEnSiBlE-A-La-cAsSe.

En outre, vous devrez peut-être ajouter le chemin du champ au <fieldset> parent :

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

Non lié à un formulaire

Par exemple, lorsque vous avez besoin d'un champ en liste déroulante dans un composant en que filtre admin/site.

//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!!

Substitution de getLabel()

Comme mentionné à la section Exigences de classe pour les types de champ de formulaire, les type de champ de formulaire personnalisés ne définissent généralement pas leur propre getLabel(). Si vous souhaitez créer une étiquette personnalisée, vous pouvez toujours utiliser getLabel() dont chaque classe de type de champ hérite de JFormField, par exemple en la définissant comme suit :

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

Ce code va souligner les étiquettes de votre formulaire. (Veuillez noter que si vous souhaitez souligner vos étiquettes, l'utilisation de CSS est une meilleure option.)

Si vous souhaitez faire quelque chose de complètement différent, vous pouvez bien sûr le substituer complètement :

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; 
}

Cet exemple va ajouter une case à cocher à l'étiquette.