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 50% complete.
Outdated translations are marked like this.
Other languages:
Deutsch • ‎English • ‎Nederlands • ‎español • ‎français
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.

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.

Setting the Values of a List Option and Using JSON or an API Instead of a Database Call

If you want to use an API call instead of a database call to build a custom list item, use the following code.

This field was created in a module. To get it to work it needs to be saved to

  • mod_modulename/models/fields/stackexchangesites.php

The naming convention is important as it is used within our function name.

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

// call the list field type
JFormHelper::loadFieldClass('list');

// The class name must always be the same as the filename (in camel case)
// extend the list field type
class JFormFieldStackexchangesites extends JFormFieldList
{
    //The field class must know its own type through the variable $type.
    protected $type = 'Stackexchangesites';

    // get the options for the list field
    public function getOptions()
    {
       // insert your JSON here or else call an API
        $json = {"api_site_parameter":"meta.stackoverflow","site_url":"https://meta.stackoverflow.com"~}
        // decode the JSON
        $sites = json_decode($json, true);
       
        // use a for each to iterate over the JSON
        foreach($sites['items'] as $site)
        {
           // choose the element of the JSON we want, and set it as a variable so we can use it in our array.
            $site = $site['api_site_parameter'];
            $site_url = $site['site_url'];
            // set an array and start adding values to it.  Set another array within our array to set our value / text items.
            $stackExchangesSitesOptions[] = array("value" => $site, "text" => $site_url);
        }

        // Merge any additional options in the XML definition.
        $options = array_merge(parent::getOptions(), $stackExchangesSitesOptions);
        return $options;
    }
}

On the front end simply calling the parameter gets us the value.

$stackexchangesites = $params->get('stackexchangesites');

Pièges

Charger un champs personnalisé peut conduire à une erreur fatale, si par exemple un champ du noyau existe avec le même nom de fichier, et que le nom de champ personnalisé étend le champ du noyau.

Considérez un fichier testfields/radio.php contenant

<?php

class TestFormFieldRadio extends JFormFieldRadio {}

L'appel de JFormHelper::loadFieldClass('radio') va générer une erreur fatale : Class 'JFormFieldRadio' not found.

Il y deux raisons pour cela.

  1. JLoader ne peut pas charger JFormFieldRadio, parce que le nom de la classe (JFormField*) ne correspond pas au chemin (joomla/form/fields/* - notez l'utilisation du pluriel pour 'fields').
  1. JFormHelper ne peut pas charger JFormFieldRadio, parce que les chemins des champs personnalisés sont parcourus en premier, et le type du champ personnalisé ('radio') a été trouvé avant que les classes du noyaux ne soient atteintes

Solution

Chargez directement le fichier du champ du noyau :

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

class TestFormFieldRadio extends JFormFieldRadio {}

et utilisez proprement JFormHelper::loadFieldClass avec 'test.radio' plutôt que '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.

Sample Component Code

Below is the code for a small component which you can install and run and can adapt to experiment further with custom fields. The component includes two custom fields

  1. A "City" field as described above (except that the options are hard-coded rather than being selected from a database). We'll also allow multiple Cities to be selected and pre-select two of them as default.
  2. A custom "time" field which maps to the HTML "time" input type and includes underlining the label and support for setting a minimum and maximum time.

Create the following five files in a folder called com_custom_fields. Then zip up this folder to create com_custom_fields.zip. Install this component on your Joomla instance. Once installed, navigate on your browser to your Joomla site and add to the URL the parameter &option=com_custom_fields. This should display the form with the two custom fields. You can submit the form and see the HTTP POST parameters using the browser development tools but the component doesn't contain any code which handles those parameters.

(As described in Basic form guide, the approach below isn't the recommended way to design Joomla components but it's written in this minimalist fashion to focus on the custom fields aspects).

com_custom_fields.xml Manifest file for the component

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.1.0" method="upgrade">

	<name>com_custom_fields</name>
	<version>1.0.0</version>
	<description>Custom Fields demo component</description>
	
	<administration>
	</administration>

	<files folder="site">
		<filename>custom_fields.php</filename>
		<filename>form_definition.xml</filename>
		<filename>City.php</filename>
		<filename>Time.php</filename>
	</files>
</extension>

custom_fields.php The main code file which is run when an HTTP GET or POST is directed towards this component.

<?php
defined('_JEXEC') or die('Restricted Access');

use Joomla\CMS\Form\Form;

$form = Form::getInstance("sample", __DIR__ . "/form_definition.xml", array("control" => "jform"));
Form::addFieldPath(__DIR__);
?>
<form action="<?php echo JRoute::_('index.php?option=com_custom_fields'); ?>"
    method="post" name="adminForm" id="adminForm" enctype="multipart/form-data">

	<?php echo $form->renderField('mytime');  ?>
	
	<?php echo $form->renderField('mycity');  ?>

	<button type="submit">Submit</button>
</form>

form_definition.xml File containing the XML for the form definition, basically the two custom fields.

<?xml version="1.0" encoding="utf-8"?>
<form name="myForm">
	<field
		name="mytime"
		type="time"
		label="Time:"
		class="inputbox"
		min="09:00"
		max="17:30"
		required="true" />
	<field name="mycity" 
		type="City"
		label="City:"
		required="true"
		multiple="true"
		class="inputbox" />
</form>

City.php PHP code for the City custom field.

<?php
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

class JFormFieldCity extends JFormFieldList {

	protected $type = 'City';

	public function getOptions() {
		$cities = array(
					array('value' => 1, 'text' => 'New York'),
					array('value' => 2, 'text' => 'Chicago'),
					array('value' => 3, 'text' => 'San Francisco'),
					);
		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $cities);

		// pre-select values 2 and 3 by setting the protected $value property
		$this->value = array(2, 3);

		return $options;
	}
}

Time.php PHP code for the time custom field.

<?php
defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;

class JFormFieldTime extends FormField
{
	protected $type = 'time';

	protected function getInput()
	{
		// get relevant attributes which were defined in the XML form definition
		$attr = !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= !empty($this->element['min']) ? ' min="' . $this->element['min'] . '"' : '';
		$attr .= !empty($this->element['max']) ? ' max="' . $this->element['max'] . '"' : '';

		// set up html, including the value and other attributes
		$html = '<input type="time" name="' . $this->name . '" value="' . $this->value . '"' . $attr . '/>';

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