JForm,  функционал, введённый в Joomla! 2.5, позволяет легко создавать HTML-формы (<form>). Формы, созданные с помощью JForm, состоят из полей формы, iреализованных как JFormField. Существует JFormField для каждого типа поля, которое вы можете найти в форме, например, тип текстового поля и тип поля даты. JForm поддерживает большой выбор стандартных типов полей. Полный список см. в разделе Стандартные типы полей формы.

Joomla! позволяет расширить стандартные типы полей или определить свои собственные. Например, если ваш компонент управляет записями телефонной книги, вы можете определить тип поля формы, который выводит список городов. Определение пользовательского типа поля формы имеет ряд преимуществ:

  • Вы сможете смешивать стандартные типы полей с пользовательскими типами полей в форме на основе JForm.
  • В итоге у вас получится пакет кода многократного использования, который можно легко использовать во всем коде.
  • Расширения, которые взаимодействуют с вашим расширением, смогут создавать поля формы, не вмешиваясь в таблицы вашей базы данных и другие внутренние компоненты.

Тип поля формы. Требования к классу.

Тип поля формы определяется в классе , который должен быть (не обязательно прямым) подклассом JFormField. Для корректной работы класс должен определять как минимум три метода:

  • public function getLabel()
    Эта функция будет вызвана для создания метки, принадлежащей вашему полю, и должна возвращать HTML-строку, содержащую её. Поскольку JFormField определяет готовую к использованию реализацию getLabel() , пользовательские типы полей формы обычно не определяют свои собственные getLabel(). Если вы его исключите, будет использоваться унаследованный метод создания меток. Рекомендуется исключить метод getLabel() для согласованности и скорости, если вы действительно не хотите изменить HTML метки.
  • public function getInput()
    Эта функция будет вызвана для создания самого поля и должна вернуть HTML-строку, содержащую его. Это также то, где обычно происходит большая часть обработки. В нашем примере поля города телефонной книги эта функция должна будет получить список доступных городов и вернуть HTML- <select> с городами, вставленными как <option>s.
  • public function getValue()
    Эта функция будет вызвана для получения значения поля. Значение получается из функции LoadFormData в модели

Замечание: В статье все эти методу обозначены как public, но в родительских классах JFormField, JFormFieldList  они - protected, так что, возможно здесь вкралась ошибка.

Внутри кода необходимо обработать атрибуты, заданные пользователем поля в определениях ФОРМЫ XML. Некоторые из этих атрибутов доступны через защищенные переменные-члены JFormField. Например, атрибут name доступен в коде как $this->name. Аналогичным образом, label, description, default, multiple иclass также доступны в качестве свойств $this. Другие параметры, которые вы могли определить, могут быть доступны через массив: $this->element ,атрибут size будет в $this->element['size'].

Какой класс расширять?

Чтобы тип поля формы можно было использовать в JForm, он должен быть подклассом JFormField. Однако он не обязательно должен быть прямым потомком этого класса. Можно также расширить существующий (стандартный или настраиваемый) тип поля формы и тем самым наследовать полезный код.

Если тип поля формы очень похож на существующий тип, следует расширить этот тип. Особенно, если тип поля формы является списком, пожалуйста, подкласс JFormFieldList. Вам нужно только переопределить метод getOptions() , чтобы вернуть отображаемые параметры. Метод getInput() преобразует эти параметры в HTML.

Чтобы расширить существующий тип, например JFormFieldList, загрузите его, добавив следующее после jimport('joomla.form.formfield');:

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

Если тип поля формы отличается от любого существующего типа, надо расширять JFormField напрямую.

Расположение файлов.

  • Стандартные типы полей формы расположены в libraries/joomla/form/fields/. Не следует хранить там создаваемые поля, а также не следует использовать этот путь в собственном коде. Стандартные типы обычно являются хорошими примерами.
  • Типы пользовательских полей, принадлежащие компоненту, обычно находятся в каталоге administrator/components/<name of your component>/models/fields. В коде можно указать этот или другой путь:
    JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
  • XML-файлы, определяющие формы, обычно находятся в administrator/components/<name of your component>/models/forms. Используйте что-то вроде следующего фрагмента, чтобы указать путь к формам:
    JForm::addFormPath(JPATH_COMPONENT . '/models/forms');

Соглашения об именовании и скелет.

В этом разделе <ComponentName> представляет из себя имя компонента в camel-cased, а <FieldName> — имя типа поля формы в camel-cased. Класс поля должен быть помещен в administrator/components/<name of your component>/models/fields/<name of your field>.php и выглядеть следующим образом:

<?php
// Проверка, запущен ли этот файл в Joomla!
defined('_JEXEC') or die('Restricted access');

jimport('joomla.form.formfield');

// Имя класса всегда должно совпадать с именем файла (в camel case)
class JFormField<FieldName> extends JFormField {

	//Класс поля должен знать свой собственный тип через переменную $type.
	protected $type = '<FieldName>';

	public function getLabel() {
		// код, возвращающий HTML, который будет показан в качестве метки
	}

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

Группировка типов пользовательских полей.

Предупреждение: эта информация частично неверна и нуждается в доработке.

Пользовательские типы полей могут быть сгруппированы с помощью подчеркивания в названии полей. Класс полей с именем, например, "JFormFieldMy_randomField" должен храниться в administrator/components/<name of your component>/models/fields/my/randomField.php. Мы можем снабдить префиксом имена полей нашей формы именем группы, затем поставить знак подчеркивания и затем имя поля.

Пример типа пользовательского поля.

Предположим, вы работаете над компонентом с именем com_phonebook и хотите определить поле, содержащее города. Создайте файл administrator/components/com_phonebook/models/fields/city.php и напишите в нем что-то похожее на следующее:

<?php
// Проверка, запущен ли этот файл в Joomla!
defined('_JEXEC') or die('Restricted access');

jimport('joomla.form.formfield');

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

	// getLabel() убран

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

Более продвинутый подход заключается в расширении класса JFormFieldList . Предположим, вы хотите создать выпадающий список городов динамически из базы данных на основе динамического условия. Это можно сделать следующим образом:

<?php
// Проверка, включен ли этот файл в 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');
                $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;
                }
                // Объедините любые дополнительные опции в XML-определении.
        $options = array_merge(parent::getOptions(), $cities);
                return $options;
    }
}

В приведенном выше примере показан простой запрос, который выведет список городов из таблицы, в которой название города относится к его стране соответственно. Вы можете создать выпадающий список на основе более сложных запросов.

Установка значений параметра списка и использование JSON или API вместо вызова базы данных.

Если вы хотите использовать вызов API вместо вызова базы данных для создания пользовательского элемента списка, используйте следующий код.

Это поле было создано в модуле. Чтобы он работал, его нужно сохранить в файл

  • mod_modulename/models/fields/stackexchangesites.php

Соглашение об именовании важно, поскольку оно используется в имени нашей функции.

<?php
// Проверка, включен ли этот файл в Joomla!
defined('_JEXEC') or die('Restricted access');

// вызвать тип поля списка
JFormHelper::loadFieldClass('list');

// Имя класса всегда должно быть таким же, как имя файла (в верблюжьем регистре), 
// расширяя тип поля списка
class JFormFieldStackexchangesites extends JFormFieldList
{
    //Класс поля должен знать свой собственный тип через переменную $type.
    protected $type = 'Stackexchangesites';

    // получить параметры для поля со списком
    public function getOptions()
    {
       // вставьте сюда свой JSON или вызовите API
        $json = {"api_site_parameter":"meta.stackoverflow","site_url":"https://meta.stackoverflow.com"~}
        // декодировать JSON
        $sites = json_decode($json, true);
       
        // использовать foreach для итерации по JSON
        foreach($sites['items'] as $site)
        {
           // выбрать нужный нам элемент JSON и установить его как переменную, чтобы мы могли 
           // использовать его в нашем массиве.
            $site = $site['api_site_parameter'];
            $site_url = $site['site_url'];
            // задать массив и начать добавлять в него значения.  Установите другой массив внутри 
            // нашего массива для установки значений / текстовых элементов.
            $stackExchangesSitesOptions[] = array("value" => $site, "text" => $site_url);
        }

        // Объединить любые дополнительные опции в XML-определении.
        $options = array_merge(parent::getOptions(), $stackExchangesSitesOptions);
        return $options;
    }
}

На фронэнде простое обращение к параметру дает нам значение.

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

Подводные камни.

Загрузка пользовательского поля может привести к фатальной ошибке, если существует основное поле с тем же именем файла, а пользовательское поле расширяет основное поле.

Рассмотрим файл testfields/radio.php, содержащий

<?php

class TestFormFieldRadio extends JFormFieldRadio {}

Вызов JFormHelper::loadFieldClass('radio') приведет к фатальной ошибке: Класс 'JFormFieldRadio' не найден.

На это есть две причины.

  1. JLoader не может автозагрузить JFormFieldRadio , потому что имя класса (JFormField*) не соответствует имени пути (joomla/form/fields/* - обратите внимание на множественное число в fields).
  2. JFormHelper не может загрузить JFormFieldRadio , потому что пользовательские пути сканируются первыми и запрашиваемый тип поля('radio') разрешается до того, как будут достигнуты классы ядра.

Решение.

Требуется непосредственно файл основного поля:

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

class TestFormFieldRadio extends JFormFieldRadio {}

и правильно использовать JFormHelper::loadFieldClass с 'test.radio' вместо 'radio'.

Использование типа пользовательского поля.

Привязка к форме.

Чтобы использовать тип поля City, нам необходимо обновить XML-файл, содержащий поля формы. Откройте XML-файл, расположенный в папке administrator/components/com_phonebook/models/forms, и добавьте поле обычным способом:

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

Имя атрибута - cAsE-sEnSiTiVe.

Кроме того, может потребоваться добавить путь к полю в родительский <fieldset>:

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

Без привязки к форме.

Например, когда вам нужно поле в качестве выпадающего списка в компоненте в качестве фильтра admin/site.

//Получить пользовательское поле
JFormHelper::addFieldPath(JPATH_COMPONENT . '/models/fields');
$cities = JFormHelper::loadFieldType('City', false);
$cityOptions=$cities->getOptions(); // работает только если вы объявили getOptions как public!!!

Переопределение getLabel()

Как упоминалось в разделе Требования к классу типа поля формы, пользовательские типы полей формы обычно не определяют свою собственную getLabel(). Если вы все же хотите создать пользовательскую метку, вы можете воспользоваться функцией getLabel(), которую каждый класс типа поля наследует от JFormField, определив ее следующим образом:

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

Этот код подчеркнет ярлыки (label) вашей формы. (Обратите внимание, что если ваша цель - подчеркнуть метки формы, предпочтительнее использовать CSS).

Если вы хотите сделать что-то совершенно другое, вы, конечно, можете полностью переопределить его:

public function getLabel() {
	// Инициализация переменных.
	$label = '';
	$replace = '';

	// Получение текста метки из элемента XML, по умолчанию используется имя элемента.
	$text = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];

	// Постройте класс для метки.
	$class = !empty($this->description) ? 'hasTip' : '';
	$class = $this->required == true ? $class.' required' : $class;
		
	// Добавить флажок замены
	$replace = '<input type="checkbox" name="update['.$this->name.']" value="1" />';
		
	// Добавить открывающий тег label и атрибуты main attributes.
	$label .= '<label id="'.$this->id.'-lbl" for="'.$this->id.'" class="'.$class.'"';

	// Если указано описание, создать всплывающую подсказку.
	if (!empty($this->description)) {
		$label .= ' title="'.htmlspecialchars(trim(JText::_($text), ':').'::' .
				JText::_($this->description), ENT_COMPAT, 'UTF-8').'"';
	}

	// Добавить текст метки и закрывающий тег.
	$label .= '>'.$replace.JText::_($text).'</label>';
	
	return $label; 
}

В этом примере внутри метки будет добавлен флажок.

Образец кода компонента.

Ниже приведен код небольшого компонента, который вы можете установить и запустить, а также адаптировать для дальнейших экспериментов с пользовательскими полями. Компонент включает два пользовательских поля.

  1. Поле "City" , как описано выше (за исключением того, что опции жестко закодированы, а не выбираются из базы данных). Мы также разрешим выбирать несколько городов и предварительно выберем два из них по умолчанию.
  2. Пользовательское поле  "time", которое соответствует HTML типу input "time" и включает подчеркивание метки и поддержку установки минимального и максимального времени.

Создайте следующие пять файлов в папке с именем com_custom_fields. Затем заархивируйте эту папку, чтобы создать com_custom_fields.zip. Установите этот компонент на ваш экземпляр Joomla. После установки перейдите в браузере на ваш сайт Joomla и добавьте в URL параметр &option=com_custom_fields. Это должно отобразить форму с двумя пользовательскими полями. Вы можете отправить форму и увидеть параметры HTTP POST с помощью средств разработки браузера, но компонент не содержит кода, который обрабатывает эти параметры.

(Как описано в Общем руководстве по формам, приведенный ниже подход не является рекомендуемым способом проектирования компонентов Joomla, но он написан в этой минималистской манере, чтобы сосредоточиться на аспектах пользовательских полей).

com_custom_fields.xml файл манифеста для компонента

<?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 Основной файл кода, который запускается, когда HTTP GET или POST направлен на этот компонент.

<?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 Файл, содержащий XML для определения формы, в основном два настраиваемых поля.

<?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-код для настраиваемого поля City.

<?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'),
					);
		// Объединить любые дополнительные опции в XML-определении.
		$options = array_merge(parent::getOptions(), $cities);

		// предварительно выбрать значения 2 и 3, установив защищенное свойство $value
		$this->value = array(2, 3);

		return $options;
	}
}

 

Time.php PHP код для настраиваемого поля time.

<?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()
	{
		// получить соответствующие атрибуты, которые были определены 
                // в определении формы XML
		$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'] . '"' : '';

		// установить html, включая значение и другие атрибуты
		$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>';
	}
}

 

Оригинал.