Crear un campo de formulario tipo personalizado

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
series
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, una característica introducida en Joomla! 2.5, permite crear fácilmente formularios HTML (<form>). Los formularios creados con JForm consisten en campos de formulario, implementados como JFormField. Hay un JFormField para cada tipo de campo que se puede encontrar en un formulario, como un campo tipo texto y un campo tipo fecha. JForm soporta una gran selección campos tipo estándar. Para una lista completa, ver Campos de formulario, Tipos Estándar.

Joomla! 2.5 hace posible extender el campo de formulario tipo estándar o definir el tuyo propio. Por ejemplo, si el componente gestiona las entradas de la libreta de teléfonos, es posible que desees definir un campo de formulario tipo que genere una lista de selección de ciudades. Hay varias ventajas en la definición de un campo de formulario tipo personalizado:

  • Serás capaz de mezclar campo tipo estándar con el campo tipo personalizado en un JForm.
  • Llegarás a tener un paquete de código reutilizable que puede ser utilizado fácilmente en todo tu código.
  • Las extensiones que colaboran con tu extensión serán capaz de crear campos de formulario sin entrometerse con tus tablas de base de datos y otros componentes internos.

Un campo de formulario tipo se define en clase, que debe ser (no necesariamente directa) subclase de JFormField. Para que funcione correctamente, la clase debe definir al menos tres métodos:

  • public function getLabel()
  • Se llama a esta función para crear la etiqueta que pertenece a tu campo y debe devolver una cadena HTML que la contiene. Desde JFormField se define un lista-para-usar la implementación getLabel(), los campos de formularios de tipo personalizados no suelen definir sus propias getLabel(). Si quiere hacerlo, el método heredado de creación de etiquetas será utilizado. Se recomienda dejar el método getLabel() para consistencia y velocidad a menos que realmente desees modificar la etiqueta HTML.
  • public function getInput()
  • Se llama a esta función para crear el campo en sí mismo y debe devolver una cadena HTML que lo contiene. Este es también el lugar donde habitualmente ocurre la mayoría de la transformación. En el campo Ciudad de nuestra libreta de teléfonos de ejemplo, esta función tendrá que recuperar una lista de las ciudades y devolver un <select> HTML con las ciudades insertadas como una lista de <option>.
  • public function getValue()
    Se llamará a esta función para obtener el valor del campo. El valor se obtiene de la función LoadFormData en el Modelo

Dentro de tu código, tendrás que procesar el conjunto de atributos para el campo del usuario en el archivo XML de definición del formulario. Algunos de estos atributos son accesibles a través de miembros protegidos de variables de JFormField. Por ejemplo, el atributo name está disponible en el código como $this->name. Del mismo modo, label, description, default, multiple y class están también disponibles como propiedades de $this. Otros parámetros que hayas definido se puede acceder a través del arrary $this->element: el atributo size estará en $this->element['size'].

¿Cuál clase a subclase?

Para que un campo de formulario tipo sea utilizable en JForm, debe ser una subclase de JFormField. Sin embargo, no tiene que ser una hija directa esa clase: también puede una subclase de otro campo de formulario tipo existente (estándar o personalizada) y por tanto heredar código útil.

Si el campo de formulario tipo es bastante similar a la de un tipo existente, la subclase de heredar de ese tipo. Especialmente si el tipo de campo de formulario es una lista, por favor vea la subclase JFormFieldList. Sólo tienes que reemplazar el método getOptions() para devolver las opciones a ser mostradas; el método getInput() va a convertir estas opciones a HTML.

Una subclase de un tipo existente, por ejemplo JFormFieldList, se la carga mediante la adición de lo siguientes después de jimport('joomla.form.formfield');:

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

Si el campo de formulario tipo es diferencia de cualquier tipo existente, cargar directamente la subclase JFormField.

Ubicación de los archivos

  • Los archivos de los campos de formulario tipo estándar se encuentra en libraries/joomla/form/fields/. No se deben almacenar los campos personalizados aquí, ni debes usar esta ruta en tu propio código, pero los tipos estándar generalmente son buenos ejemplos.
  • Los campos de tipo personalizado que pertenecen a tus componentes están situados generalmente en administrator/components/<nombre de tu componente>/models/fields. Puedes especificar este u otra ruta en tu código:
JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
  • Los archivos XML que definen los formularios están generalmente ubicados en administrator/components/<nombre de tu componente>/models/forms. Usar algo como el siguiente fragmento de código para especificar una ruta de acceso a los formularios:
JForm::addFormPath(JPATH_COMPONENT . '/models/forms');

Convenciones de nombres y esqueleto

En esta sección, <ComponentName> representa el nombre del componente en grafía MayúsculasMinúsculas y <FieldName> representa el nombre del campo de formulario tipo en grafía MayúsculasMinúsculas. La clase del campo debe ser colocada en administrator/components/<nombre de tu componente>/models/fields/<nombre de tu campo>.php, y se debe ver como algo así:

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

Agrupar campos tipo personalizados

Advertencia: esta información es parcialmente incorrecta y debe ser mejorada.

Los campos tipo personalizados se pueden agrupar mediante el uso de un guión bajo en el nombre de campo. Un clase de campo con un nombre como por ejemplo "JFormFieldMy_randomField" debe ser almacenado en administrator/components/<nombre de tu componente>/models/fields/my/randomField.php. Podemos prefijar nuestros nombres de los campo de formulario con algunos nombre del grupo, a continuación, ponemos un carácter de subrayado y, a continuación, un nombre de un campo.

Un ejemplo de campo tipo personalizado

Supongamos que estás trabajando en su componente denominado com_phonebook, y deseas definir un campo que contenga las ciudades. Creamos el archivo administrator/components/com_phonebook/models/fields/city.php y escribimos algo similar a lo siguiente:

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

Una forma más avanzada de enfoque es la extensión de la clase JFormFieldList. Supongamos que deseas crear una lista desplegable de ciudades de forma dinámica a partir de la base de datos basado en una condición dinámica, entonces puedes hacer esto de la siguiente manera:

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

El ejemplo anterior muestra una consulta simple que mostrará una lista de ciudades desde una tabla donde el nombre de la ciudad pertenece a su país, respectivamente. Puedes crear una lista desplegable basado en consultas más complejas.

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');

Escollos

Cargar un campo personalizado puede provocar un error fatal, si existe un campo en el código base con el mismo nombre de archivo y el campo personalizado amplía el campo del código base.

Considere un archivo testfields/radio.php que contenga

<?php

class TestFormFieldRadio extends JFormFieldRadio {}

Llamar a JFormHelper::loadFieldClass('radio') producirá un error fatal: no se encuentra la clase 'JFormFieldRadio'.

Hay dos razones para esto.

  1. JLoader no puede cargar automáticamente <código>JFormFieldRadio, porque el nombre de clase (<código>JFormField*) no coincide con el nombre de ruta (joomla/form/fields/* - observe el plural en fields).
  1. JFormHelper no puede cargar <código>JFormFieldRadio, porque las rutas personalizadas se escanean primero, y el tipo de campo solicitado ('radio') se resuelve antes de que se alcancen las clases principales

Solución

Requiera el archivo de campo del código base directamente:

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

class TestFormFieldRadio extends JFormFieldRadio {}

y use JFormHelper::loadFieldClass correctamente con 'test.radio' en lugar de 'radio'.

Utilizar el campo tipo personalizado

Enlazado con un formulario

Para utilizar campo tipo city, necesitamos actualizar el archivo XML que contiene los campos de formulario. Abre tu archivo XML que se encuentra en administrator/components/com_phonebook/models/forms y agregar el campo en la forma habitual:

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

El atributo name es sensible a mayúsculas/minúsculas.

Además, puedes necesitar agregar al campo la ruta de acceso al <fieldset> padre:

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

No enlazado con un formulario

Por ej.ː cuando necesitas el campo como una lista desplegable en un componente como admin/filtro del sitio.

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

Reemplazar getLabel()

Como se mencionó en la sección Campo de formulario tipo, requisitos de clase, los campos de formulario tipos personalizados no suelen definir sus propios getLabel(). Si deseas crear una etiqueta personalizada, todavía puedes hacer uso del getLabel() que cada campo tipo hereda de la clase JFormField, por ejemplo, definiéndolo de la siguiente manera:

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

Este código subrayará las etiquetas del formulario. (Ten en cuenta que si tu objetivo es subrayar etiquetas del formulario, usando CSS es la manera preferida.)

Si quieres hacer algo completamente diferente, por supuesto, también puede reemplazarlo completamente:

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

En este ejemplo se va a agregar una casilla de verificación dentro de la etiqueta.

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