Введение.

Это одно из серии руководств по API, ель которых - помочь вам понять, как использовать API Joomla, предоставляя подробные объяснения и примеры кода, которые вы можете легко установить и запустить.

Класс Joomla Form и API позволяют вам легко разрабатывать HTML-формы и обрабатывать вводимые пользователем данные.

На этой странице описывается, как вы взаимодействуете в целом с классом Joomla Form, и приводится код простого компонента, который можно установить для демонстрации использования этого API. Сопутствующее руководство  Расширенное руководство по формам посвящено более продвинутым аспектам класса Joomla Form.

Сама Joomla также имеет особый способ проектирования функциональности форм, и эта страница включает обучающий материал, который поможет вам понять и разработать ваш компонент в соответствии с этими принципами.

Общее описание.

На схеме ниже показано базовое использование класса Joomla Form.

Using Joomla Forms

Во-первых, вам нужно определить вашу форму в XML, используя стандартные типы полей формы В качестве примера смотрите код компонента ниже. В основных терминах, каждый элемент поля в вашем XML-файле соответствует элементу HTML (в основном "input") в форме, при этом атрибуты XML-поля соответствуют атрибутам элемента (input). Многие из возможных атрибутов поля перечислены в разделе Тип поля текстовой формы.

Шаг 1 Загрузка формы.

Пользователь нажимает на URL вашей формы, и Joomla направляет HTTP GET запрос к коду вашего компонента. Вам нужно вызвать Form::getInstance(), передав имя вашего XML-файла с определением формы. Код Joomla Form создает экземпляр формы, а затем (в Form::loadFile()) считывает файл в память (как PHP SimpleXMLElement) и анализирует XML, чтобы убедиться в его достоверности. Основными параметрами, передаваемыми в getInstance(), являются:

  • name – строка, определяющая идентификатор, который будет присвоен этой форме - он должен быть уникальным, чтобы не пересекаться с другими формами Joomla на той же веб-странице
  • data – имя XML-файла, в котором содержится определение вашей формы
  • options – массив опций, в котором элемент "control" указывает имя массива, в котором будут храниться параметры POST. Например, пример кода имеет array("control"=>"myform"), что означает, что входные элементы HTML будут иметь атрибуты имени, заданные как "myform[message]", "myform[email]" и т.д. Таким образом, они будут передаваться в параметрах POST, и тогда их очень легко поместить в массив PHP.

Шаг 2 Предоставление данных перед заполнением.

Вы предоставляете значения для любого элемента формы по своему усмотрению. Например, если эта форма используется для редактирования записи в базе данных, то вы должны предварительно заполнить ее значениями существующих полей из базы данных. Вы можете предоставить значения, создав ассоциативный массив $data и передав его в метод Form::bind($data), а Joomla Form затем сохранит эти данные локально в экземпляре формы.

Шаг 3 Вывод формы в HTML.

Вы вызываете renderField(fieldName) на экземпляре Form, и вам возвращается HTML, который вы можете вывести на экран. Joomla обрабатывает XML-представление вашей формы, чтобы получить раздел, относящийся к переданному fieldName, генерирует HTML для этого HTML-элемента и включает атрибут "value" на основе предварительно заполненных данных, которые вы передали в шаге 2. При выводе формы необходимо также окружить элементы ввода элементом <form> и добавить кнопку отправки.

Шаг 4 Пользователь отправляет форму.

Пользователь вводит данные в HTML-форму и нажимает кнопку отправки. Браузер генерирует HTTP POST-запрос к URL-адресу, указанному в элементе <form>, и передает серверу в параметрах POST значения, введенные пользователем; каждый параметр имеет ключ атрибут «name» элемента ввода HTML.

Joomla направляет этот POST через ваш компонент. Поскольку это новый HTTP-запрос, предыдущий экземпляр Form больше не существует, поэтому вам придется снова вызвать Form::getInstance(), передав имя XML-файла формы, и Joomla (как и раньше) создает экземпляр Form и считывает этот файл в память.

Шаг 5 Обработка данных HTTP POST.

На этом шаге вы обрабатываете отправленные данные. Это включает в себя:

  1. Получение данных POST с помощью Factory::getApplication->input->get(). Обычно атрибуты "name" входных элементов определяются таким образом, чтобы параметры POST отображались в виде массива, и вы можете использовать фильтр ARRAY из  JInput - получение и фильтрация переменных запроса, для их считывания непосредственно в массив PHP. Однако это означает, что отдельные элементы вообще не фильтруются.
  2. Фильтрация. При этом применяется фильтрация к каждому из входных значений. Фильтр, примененный к полю, управляется параметром "filter=..." в этом поле в XML-файле формы или, если его нет, фильтр по умолчанию удалит HTML-теги и т. д. из значений данных. Возможные фильтры находятся в методе filterField() класса Form (см. https://joomla.stackexchange.com/questions/5764/what-are-possible-filters-in-joomla-form-fields). Обратите внимание, что эти фильтры полей форм отличаются от фильтров jinput.
  3. Валидация. Вы проверяете данные, которые ввел пользователь, вызывая validate($data) на вашем экземпляре формы, передавая ассоциативный массив (отфильтрованных) данных пользователя. Joomla сравнивает введенные пользователем данные с проверкой, которую вы определили в XML-файле вашей формы, и генерирует ошибки для полей, которые не прошли проверку.

Если есть ошибки валидации, то вы должны отобразить эти ошибки пользователю и заново отобразить форму, предварительно заполнив поля теми (отфильтрованными) данными, которые пользователь ввел ранее.

Если ошибок нет, вы можете подтвердить это пользователю и показать следующую веб-страницу.

Образец формы 1.

Ниже приведен код небольшого компонента, который вы можете установить для демонстрации базового использования форм Joomla. Поместите следующие 3 файла в папку под названием "com_sample_form1". Затем застегните папку, чтобы создать com_sample_form1.zip и установите его как компонент на вашем экземпляре Joomla.

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

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

	<name>com_sample_form1</name>
	<version>1.0.0</version>
	<description>Sample form 1</description>
	
	<administration>
	</administration>

	<files folder="site">
		<filename>sample_form1.php</filename>
		<filename>sample_form.xml</filename>
	</files>
</extension>

sample_form.xml Файл, содержащий XML для определения формы

<?xml version="1.0" encoding="utf-8"?>
<form>
	<field
		name="message"
		type="text"
		label="Enter message"
		size="40"
		class="inputbox"
		required="true" />
	<field name="email" 
		type="email"
		label="Enter email"
		required="true"
		size="40"
		class="inputbox" />
	<field name="telephone" 
		type="tel"
		label="Enter telephone number"
		required="true"
		size="40"
		class="inputbox"
		validate="tel" />
</form>

sample_form1.php Код компонента.

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

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

$form = Form::getInstance("sample", __DIR__ . "/sample_form.xml", array("control" => "myform"));
$prefillData = array("email" => ".@.");

if ($_SERVER['REQUEST_METHOD'] === 'POST') 
{
	$app   = JFactory::getApplication();
	$data = $app->input->post->get('myform', array(), "array");
	echo "Message was " . $data["message"] . 
		", email was " . $data["email"] . 
		", and telephone was " . $data["telephone"] . "<br>";
	$filteredData = $form->filter($data);
	$result = $form->validate($filteredData);
	if ($result)
	{
		echo "Validation passed ok<br>";
	}
	else
	{
		echo "Validation failed<br>";
		$errors = $form->getErrors();
		foreach ($errors as $error)
		{
			echo $error->getMessage() . "<br>";
		}
		// в повторно отображаемой форме показывать то, что ввел пользователь (после фильтрации данных)
		$prefillData = $filteredData;
	}
}

$form->bind($prefillData);
?>
<form action="<?php echo JRoute::_('index.php?option=com_sample_form1'); ?>"
    method="post" name="sampleForm" id="adminForm" enctype="multipart/form-data">

	<?php echo $form->renderField('message');  ?>
	
	<?php echo $form->renderField('email');  ?>
	
	<?php echo $form->renderField('telephone');  ?>
	
	<button type="submit">Submit</button>
</form>

После установки перейдите на свой сайт и добавьте следующий параметр в URL: &option=com_sample_form1. После этого на экране появится форма с 3 обязательными полями:

  • общее поле ввода текста для сообщения
  • поле ввода электронной почты, предварительно заполненное строкой ".@."
  • поле ввода телефонного номера.

и, используя средства разработки браузера, вы можете сравнить атрибуты html с атрибутами в определении формы XML.

Обратите внимание, что современные браузеры выполняют некоторую проверку вводимых значений, в частности, они проверяют адрес электронной почты и заставляют вас вводить что-то в поля с "required". установлен атрибут, но не делают (в настоящее время) валидацию полей телефонных номеров.

Как только вы введете правильные данные в поля и нажмете кнопку Submit, данные будут отправлены на сервер и будет запущена нога POST кода примера. Это запускает процедуры фильтрации и проверки. Если есть ошибки валидации, то код выводит сообщения об ошибках и заполняет форму данными, которые ввел пользователь, а затем отображает ее заново.

MVC и другие соображения.

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

Разделение Joomla MVC.

В общих чертах Joomla разделяет компоненты на отдельные типы функциональности:

  • the controller contains the "business logic" of the application, including deciding what view and model to use
  • the model provides access to the data
  • the view decides what data is necessary for outputting on the web page and obtains this data from the model
  • the layout outputs the HTML, and includes in the output the data which has been collated by the view. The layout runs within the context of the view, and so has direct access to the variables of the view code.

Post/Request/Get pattern

In Joomla all of the HTML output (such as the display of a form) is performed in response to an HTTP GET, following the https://en.wikipedia.org/wiki/Post/Redirect/Get pattern. The sample code above doesn't follow this pattern, but instead outputs the validation errors and re-displays the form in the response to the HTTP POST request.

To follow the Joomla pattern, in the code which handles the POST we should include a HTTP GET redirect to the form URL. As that GET will then be a new HTTP request/response we must store in the user session the data to be shown when the form is redisplayed:

  • the validation error messages are stored and output using the enqueueMessage() method (which stores data in the user session automatically for us)
  • the user-entered data is stored using setUserState() and retrieved using getUserState(), and keyed by a context which should be unique to this form. The code which provides the data for the form bind() operation must first check using getUserState() if there is any prefill data in the session. And if the user enters data which successfully passes validation then setUserState() should be called to clear this prefill data in the session, otherwise it will appear whenever the user next displays the form.

Separate Controllers

In response to a GET or POST, Joomla always runs the same component file, sample_form1.php in the sample component example above and the top-level sample_form2.php in the example below. The sample_form2.php code below follows the example of the core Joomla components, and has code which passes control to different methods in different controllers based on the value of the HTTP parameter task. This parameter is set by Joomla core javascript based on the submit button, eg in the example below:

onclick="Joomla.submitbutton('myform.submit')"

The task parameter is in this example set to "myform.submit". In general the task parameter is of the form "firstpart.secondpart" and for a component called "com_example" Joomla will try to run an instance method called secondpart() of a controller class ExampleControllerFirstpart in a file firstpart.php in the controllers directory.

If the task parameter is not set then Joomla will try to run the display() method of the ExampleController class which it will expect to find in controller.php.

In terms of code:

$controller = JControllerLegacy::getInstance('Sample_form2');

uses the task parameter to identify the appropriate controller file to include, and then creates an instance of that controller class.

$controller->execute($input->getCmd('task'));

executes the appropriate method (secondpart() - or in the case below, submit() - or display()) of that class.

In this way controller functionality handling GETs (generally in the file controller.php) is separated from that handling POSTs (generally in the files in the controllers folder).

Joomla MVC classes

Joomla provides feature-rich controller, view and model classes from which your component controllers, views and models can inherit. The model code below inherits from FormModel which shields somewhat the Joomla Form API. In this case our model calls FormModel::loadForm(), and this then executes a callback to our loadFormData() to provide the data to bind() to the form.

You can also use FormController and AdminController classes, but these assume that your component is using a database table to store the data. The Joomla MVC Component Development tutorial takes this approach in J3.x:Developing an MVC Component/Adding backend actions.

Security Token

Joomla uses a security token on forms to prevent CSRF attacks (see https://en.wikipedia.org/wiki/Cross-site_request_forgery). The token is output in the layout file

<?php echo JHtml::_('form.token'); ?>

and checked within the controller handling the POST:

$this->checkToken();

If the token is found to be invalid then checkToken() outputs a warning and redirects the user back to the previous page.

Client-side Validation

In addition to the server-side validation included through the form XML definition, Joomla also provides a way of including javascript which performs client-side validation on the browser. This is NOT included below, as it's deemed beyond the scope of this tutorial, but you can find details of it in J3.x:Developing an MVC Component/Adding verifications and Client-side form validation.

Sample Form 2

This second sample component incorporates the design decisions described above and structures the code according to the Joomla paradigm. The overall file structure is shown in the diagram below.

Sample Form 2 file structure.

Starting at the bottom of this diagram and going upwards the file contents are:

com_sample_form2/sample_form2.php Entry point for the component. This is the first file which Joomla runs.

<?php
defined('_JEXEC') or die('Restricted access');
// Load the appropriate controller class
$controller = JControllerLegacy::getInstance('Sample_form2');

$input = JFactory::getApplication()->input;
// Run the task method, or display() if no task parameter
$controller->execute($input->getCmd('task'));
 
$controller->redirect();

 

com_sample_form2/controller.php The controller for handling HTTP GET requests

<?php
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\MVC\Controller\BaseController;

class Sample_form2Controller extends BaseController
{
	// Joomla will look for this class within the controller.php file
	// It will (usually) call the display() method, and will find this is in the BaseController class
}

 

com_sample_form2/sample_form2.xml Manifest file for the component.

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

	<name>com_sample_form2</name>
	<version>1.0.0</version>
	<description>Sample form 2</description>
	
	<administration>
	</administration>

	<files folder="site">
		<filename>sample_form2.php</filename>
		<filename>controller.php</filename>
		<folder>controllers</folder>
        <folder>views</folder>
        <folder>models</folder>
	</files>
</extension>

 

com_sample_form2/views/form/view.html.php View file for displaying the form. The controller display() function will create instances of the model and view, and call the view display() function below.

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

use Joomla\CMS\MVC\View\HtmlView;
use Joomla\CMS\Factory;

class Sample_form2ViewForm extends HtmlView
{
	public function display($tpl = null)
	{
		if (!$this->form = $this->get('form'))
		{
			echo "Can't load form<br>";
			return;
		}
		parent::display($tpl);	// this will include the layout file edit.php
	}
}

 

com_sample_form2/views/form/tmpl/edit.php Layout file for displaying the form

<?php
defined('_JEXEC') or die('Restricted access');
?>
<form action="<?php echo JRoute::_('index.php?option=com_sample_form2&view=form&layout=edit'); ?>"
    method="post" name="adminForm" id="adminForm" enctype="multipart/form-data">

	<?php echo $this->form->renderField('message');  ?>
	
	<?php echo $this->form->renderField('email');  ?>
	
	<?php echo $this->form->renderField('telephone');  ?>

	<button type="button" class="btn btn-primary" onclick="Joomla.submitbutton('myform.submit')">Submit</button>
	
	<input type="hidden" name="task" />
	<?php echo JHtml::_('form.token'); ?>
</form>

 

com_sample_form2/models/form.php Model associated with displaying the form. The view get('form') gets mapped to the model getForm() method call.

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

use Joomla\CMS\MVC\Model\FormModel;

class Sample_form2ModelForm extends FormModel
{

	public function getForm($data = array(), $loadData = true)
	{
		$form = $this->loadForm(
			'com_sample_form2.sample',  // just a unique name to identify the form
			'sample_form',				// the filename of the XML form definition
										// Joomla will look in the models/forms folder for this file
			array(
				'control' => 'jform',	// the name of the array for the POST parameters
				'load_data' => $loadData	// will be TRUE
			)
		);

		if (empty($form))
		{
            $errors = $this->getErrors();
			throw new Exception(implode("\n", $errors), 500);
		}

		return $form;
	}

    protected function loadFormData()
	{
		// Check the session for previously entered form data.
		$data = JFactory::getApplication()->getUserState(
			'com_sample_form2.sample',	// a unique name to identify the data in the session
			array("telephone" => "0")	// prefill data if no data found in session
		);

		return $data;
	}
	
}

 

com_sample_form2/models/forms/sample_form.xml XML file containing the form definition

<?xml version="1.0" encoding="utf-8"?>
<form>
	<field
		name="message"
		type="text"
		label="Enter message"
		size="40"
		class="inputbox"
		required="true" />
	<field name="email" 
		type="email"
		label="Enter email"
		required="true"
		size="40"
		class="inputbox" />
	<field name="telephone" 
		type="tel"
		label="Enter telephone number"
		required="true"
		size="40"
		class="inputbox"
		validate="tel" />
</form>

 

com_sample_form2/controllers/Myform.php Controller which handles the HTTP POST

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

use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Factory;

class Sample_form2ControllerMyform extends BaseController
{   
	public function submit($key = null, $urlVar = null)
	{
		$this->checkToken();
		
		$app   = JFactory::getApplication();
		$model = $this->getModel('form');
		$form = $model->getForm($data, false);
		if (!$form)
		{
			$app->enqueueMessage($model->getError(), 'error');
			return false;
		}
		
		// name of array 'jform' must match 'control' => 'jform' line in the model code
		$data  = $this->input->post->get('jform', array(), 'array');
		
		// This is validate() from the FormModel class, not the Form class
		// FormModel::validate() calls both Form::filter() and Form::validate() methods
		$validData = $model->validate($form, $data);

		if ($validData === false)
		{
			$errors = $model->getErrors();

			foreach ($errors as $error)
			{
				if ($error instanceof \Exception)
				{
					$app->enqueueMessage($error->getMessage(), 'warning');
				}
				else
				{
					$app->enqueueMessage($error, 'warning');
				}
			}

			// Save the form data in the session, using a unique identifier
			$app->setUserState('com_sample_form2.sample', $data);
		}
		else
		{
			$app->enqueueMessage("Data successfully validated", 'notice');
			// Clear the form data in the session
			$app->setUserState('com_sample_form2.sample', null);
		}
		
		// Redirect back to the form in all cases
		$this->setRedirect(JRoute::_('index.php?option=com_sample_form2&view=form&layout=edit', false));
	}
}

 

After creating the files as described above, zip up the com_sample_form2 folder to create com_sample_form2.zip and install this component. Then navigate to your Joomla site and add into the URL the parameters &option=com_sample_form2&view=form&layout=edit. This should display the form, and should work in a similar fashion to previous sample form, except that errors etc will appear in the message part of the Joomla display.

Оригинал.