Использование конструктора форм
===============================
При создании HTML форм часто приходится писать довольно большое количество повторяющегося кода,
который почти невозможно использовать в других проектах. К примеру, для каждого поля ввода
нам необходимо вывести описание и возможные ошибки валидации. Для того чтобы сделать
возможным повторное использование подобного кода, можно использовать конструктор форм.
Общая идея
----------
Конструктор форм использует объект [CForm] для описания параметров, необходимых для
создания HTML формы, таких как модели и поля, используемые в форме, а также параметры
построения самой формы. Разработчику достаточно создать объект [CForm], задать его параметры
и вызвать метод для построения формы.
Параметры формы организованы в виде иерархии элементов формы. Корнем является
объект [CForm]. Корневой объект формы включает в себя две коллекции, содержащие
другие элементы: [CForm::buttons] и [CForm::elements]. Первая содержит кнопки
(такие как «Сохранить» или «Очистить»), вторая — поля ввода, статический текст и
вложенные формы — объекты [CForm], находящиеся в коллекции [CForm::elements]
другой формы. Вложенная форма может иметь свою модель данных и коллекции
[CForm::buttons] и [CForm::elements].
Когда пользователи отправляют форму, данные, введённые в поля ввода всей иерархии формы, включая
вложенные формы, передаются на сервер. [CForm] включает в себя методы, позволяющие
автоматически присвоить данные полям соответствующей модели и провести валидацию.
Создание простой формы
----------------------
Ниже будет показано, как построить форму входа на сайт.
Сначала реализуем действие `login`:
~~~
[php]
public function actionLogin()
{
$model = new LoginForm;
$form = new CForm('application.views.site.loginForm', $model);
if($form->submitted('login') && $form->validate())
$this->redirect(array('site/index'));
else
$this->render('login', array('form'=>$form));
}
~~~
Вкратце, здесь мы создали объект [CForm], используя конфигурацию, найденную
по пути, который задан псевдонимом `application.views.site.loginForm`.
Объект [CForm], как описано в разделе «[Создание модели](/doc/guide/form.model)»,
использует модель `LoginForm`.
Если форма отправлена, и все входные данные прошли проверку без ошибок,
перенаправляем пользователя на страницу `site/index`. Иначе выводим представление
`login`, описывающее форму.
Псевдоним пути `application.views.site.loginForm` указывает на файл PHP
`protected/views/site/loginForm.php`. Этот файл возвращает массив, описывающий
настройки, необходимые для [CForm]:
~~~
[php]
return array(
'title'=>'Пожалуйста, представьтесь',
'elements'=>array(
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'rememberMe'=>array(
'type'=>'checkbox',
)
),
'buttons'=>array(
'login'=>array(
'type'=>'submit',
'label'=>'Вход',
),
),
);
~~~
Настройки, приведённые выше, являются ассоциативным массивом, состоящим из пар имя-значение,
используемых для инициализации соответствующих свойств [CForm]. Самыми важными
свойствами, как мы уже упомянули, являются [CForm::elements] и [CForm::buttons].
Каждое из них содержит массив, определяющий элементы формы. Более детальное описание
элементов формы будет приведено в следующем подразделе.
Опишем шаблон представления `login`:
~~~
[php]
Вход
~~~
> Tip|Подсказка: Приведённый выше код `echo $form;` эквивалентен `echo $form->render();`.
> Использование более компактной записи возможно, так как [CForm] реализует магический
> метод `__toString`, в котором вызывается метод `render()`, возвращающий
> код формы.
Описание элементов формы
------------------------
При использовании конструктора форм вместо написания разметки мы, главным образом,
описываем элементы формы. В данном подразделе мы опишем, как задать свойство [CForm::elements].
Мы не будем описывать [CForm::buttons], так как конфигурация этого свойства практически
ничем не отличается от [CForm::elements].
Свойство [CForm::elements] является массивом, каждый элемент которого соответствует
элементу формы. Это может быть поле ввода, статический текст или вложенная форма.
### Описание поля ввода
Поле ввода, главным образом, состоит из заголовка, самого поля, подсказки и текста ошибки и
должно соответствовать определённому атрибуту модели. Описание поля ввода содержится в
экземпляре класса [CFormInputElement]. Приведённый ниже код массива [CForm::elements]
описывает одно поле ввода:
~~~
[php]
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
~~~
Здесь указано, что атрибут модели называется `username`, тип поля — `text` и его атрибут
`maxlength` равен 32.
Любое доступное для записи свойство [CFormInputElement] может быть настроено приведённым выше
способом. К примеру, можно задать свойство [hint|CFormInputElement::hint] для того, чтобы
показывать подсказку или свойство [items|CFormInputElement::items], если
поле является выпадающим списком или группой элементов checkbox или radio.
Если имя опции не является свойством [CFormInputElement], оно будет считаться атрибутом
соответствующего HTML-тега input. Например, так как опция `maxlength` не является
свойством [CFormInputElement], она будет использована как атрибут `maxlength` HTML-элемента
input.
Следует отдельно остановиться на свойстве [type|CFormInputElement::type].
Оно определяет тип поля ввода. К примеру, тип `text` означает, что будет использован
элемент формы `input`, а `password` — поле для ввода пароля. В [CFormInputElement]
реализованы следующие типы полей ввода:
- text
- hidden
- password
- textarea
- file
- radio
- checkbox
- listbox
- dropdownlist
- checkboxlist
- radiolist
Отдельно следует описать использование "списочных" типов `dropdownlist`, `checkboxlist`
и `radiolist`. Для них необходимо задать свойство [items|CFormInputElement::items]
соответствующего элемента input. Сделать это можно так:
~~~
[php]
'gender'=>array(
'type'=>'dropdownlist',
'items'=>User::model()->getGenderOptions(),
'prompt'=>'Выберите значение:',
),
…
class User extends CActiveRecord
{
public function getGenderOptions()
{
return array(
0 => 'Мужчина',
1 => 'Женщина',
);
}
}
~~~
Данный код сгенерирует выпадающий список с текстом «Выберите значение:» и опциями
«Мужчина» и «Женщина», которые мы получаем из метода `getGenderOptions` модели `User`.
Кроме данных типов полей, в свойстве [type|CFormInputElement::type] можно указать
класс или псевдоним пути виджета. Класс виджета должен наследовать [CInputWidget] или [CJuiInputWidget].
В ходе генерации элемента формы будет создан и выполнен экземпляр класса виджета.
Виджет будет использовать конфигурацию, переданную через настройки элемента формы.
### Описание статического текста
Довольно часто в форме, помимо полей ввода, содержится некоторая декоративная
HTML разметка. К примеру, горизонтальный разделитель для выделения определённых
частей формы или изображение, улучшающее внешний вид формы. Подобный HTML код
можно описать в коллекции [CForm::elements] как статический текст. Для этого
в [CForm::elements] в нужном нам месте вместо массива необходимо использовать строку.
Например:
~~~
[php]
return array(
'elements'=>array(
......
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'
',
'rememberMe'=>array(
'type'=>'checkbox',
)
),
......
);
~~~
В приведённом коде мы вставили горизонтальный разделитель между полями `password` и `rememberMe`.
Статический текст лучше всего использовать в том случае, когда разметка и её расположение
достаточно уникальны. Если некоторую разметку должен содержать каждый элемент формы,
лучше всего переопределить непосредственно построение разметки формы, как будет описано далее.
### Описание вложенных форм
Вложенные формы используются для разделения сложных форм на несколько связанных простых. К примеру,
мы можем разделить форму регистрации пользователя на две вложенные формы: данные для входа и
данные профиля. Каждая вложенная форма может (хотя и не обязана) быть связана с моделью данных.
В примере с формой регистрации, если мы храним данные для входа и данные профиля в
двух разных таблицах (и, соответственно, в двух моделях), то каждая вложенная форма
будет сопоставлена соответствующей модели данных. Если же все данные хранятся в
одной таблице, ни одна из вложенных форм не будет привязана к модели и обе будут
использовать модель главной формы.
Вложенная форма, как и главная, описывается объектом [CForm]. Для того чтобы
описать вложенную форму, необходимо определить элемент типа `form` в
свойстве [CForm::elements]:
~~~
[php]
return array(
'elements'=>array(
......
'user'=>array(
'type'=>'form',
'title'=>'Данные для входа',
'elements'=>array(
'username'=>array(
'type'=>'text',
),
'password'=>array(
'type'=>'password',
),
'email'=>array(
'type'=>'text',
),
),
),
'profile'=>array(
'type'=>'form',
......
),
......
),
......
);
~~~
Так же как и у главной формы, у вложенной формы необходимо задать свойство [CForm::elements].
Если вложенной форме необходимо сопоставить модель данных, то это можно сделать, задав свойство [CForm::model].
В некоторых случаях бывает полезно определить форму в объекте класса, отличного от [CForm].
К примеру, как будет показано ниже, можно расширить [CForm] для реализации своего
алгоритма построения разметки. При указании типа элемента `form`, вложенная форма
будет автоматически использовать объект того же класса, что и у главной формы.
Если указать тип элемента как, например, `XyzForm` (строка, оканчивающаяся на `Form`),
то вложенная форма будет использовать объект класса `XyzForm`.
Доступ к элементам формы
------------------------
Обращаться к элементам формы так же просто, как и к элементам массива. Свойство [CForm::elements] возвращает
объект [CFormElementCollection], наследуемый от [CMap], что позволяет получить доступ к элементам формы как к
элементам массива. Таким образом, чтобы обратиться к элементу `username` формы `login` из вышеприведённого примера,
можно использовать следующий код:
~~~
[php]
$username = $form->elements['username'];
~~~
Аналогично, для доступа к элементу `email` формы регистрации, можно использовать следующий код:
~~~
[php]
$email = $form->elements['user']->elements['email'];
~~~
Так как [CForm] реализует доступ к элементам [CForm::elements] как к массиву, можно упростить
приведённый код до:
~~~
[php]
$username = $form['username'];
$email = $form['user']['email'];
~~~
Создание вложенной формы
------------------------
Ранее мы уже описывали вложенные формы. Форма, содержащая вложенные формы, называется главной.
В данном разделе мы будем использовать форму регистрации пользователя в качестве примера
создания вложенных форм, соответствующих нескольким моделям данных. Далее данные для входа
пользователя хранятся в модели `User`, а данные профиля — в модели `Profile`.
Реализуем действие `register` следующим образом:
~~~
[php]
public function actionRegister()
{
$form = new CForm('application.views.user.registerForm');
$form['user']->model = new User;
$form['profile']->model = new Profile;
if($form->submitted('register') && $form->validate())
{
$user = $form['user']->model;
$profile = $form['profile']->model;
if($user->save(false))
{
$profile->userID = $user->id;
$profile->save(false);
$this->redirect(array('site/index'));
}
}
$this->render('register', array('form'=>$form));
}
~~~
Выше мы создаём форму, используя настройки из `application.views.user.registerForm`.
После отправки данных формы и успешной их валидации мы пытаемся сохранить модели
пользовательских данных и профиля. Мы получаем модели через свойство `model` соответствующего
объекта вложенной формы. Так как валидация уже пройдена, мы вызываем `$user->save(false)`
с параметром `false`, позволяющим не проводить её повторно. Точно так же поступаем с
моделью профиля.
Далее описываем настройки формы в файле `protected/views/user/registerForm.php`:
~~~
[php]
return array(
'elements'=>array(
'user'=>array(
'type'=>'form',
'title'=>'Данные для входа',
'elements'=>array(
'username'=>array(
'type'=>'text',
),
'password'=>array(
'type'=>'password',
),
'email'=>array(
'type'=>'text',
)
),
),
'profile'=>array(
'type'=>'form',
'title'=>'Профиль',
'elements'=>array(
'firstName'=>array(
'type'=>'text',
),
'lastName'=>array(
'type'=>'text',
),
),
),
),
'buttons'=>array(
'register'=>array(
'type'=>'submit',
'label'=>'Зарегистрироваться',
),
),
);
~~~
При задании каждой вложенной формы мы указываем свойство [CForm::title].
По умолчанию при построении HTML-формы каждая вложенная форма будет выведена в
`fieldset` с заданным нами заголовком.
Описываем очень простой код шаблона представления `register`:
~~~
[php]
Регистрация
~~~
Настройка отображения формы
--------------------
Главное преимущество при использовании конструктора форм — разделение логики
(конфигурация формы хранится в отдельном файле) и отображения (метод [CForm::render]).
Мы можем настроить рендеринг формы, переопределив метод [CForm::render] либо используя собственный файл представления.
Оба варианта позволяют не менять конфигурацию формы и использовать её повторно.
При переопределении [CForm::render] необходимо, главным образом, обойти коллекции [CForm::elements]
и [CForm::buttons] и вызвать метод [CFormElement::render] для каждого элемента. Например:
~~~
[php]
class MyForm extends CForm
{
public function render()
{
$output = $this->renderBegin();
foreach($this->getElements() as $element)
$output .= $element->render();
$output .= $this->renderEnd();
return $output;
}
}
~~~
Также можно использовать представление `_form`:
~~~
[php]
renderBegin();
foreach($form->getElements() as $element)
echo $element->render();
echo $form->renderEnd();
~~~
Для этого достаточно написать:
~~~
[php]
renderPartial('_form', array('form'=>$form)); ?>
~~~
Если стандартный рендеринг формы не подходит (к примеру, в форме
нужны уникальные декоративные элементы для определённых полей),
в представлении можно поступить следующим образом:
~~~
[php]
какие-нибудь сложные элементы интерфейса
какие-нибудь сложные элементы интерфейса
какие-нибудь сложные элементы интерфейса
~~~
В этом случае конструктор форм не очень эффективен, так как нам приходится описывать
те же объёмы кода формы. Тем не менее, преимущество есть. Оно в том, что форма,
описанная в отдельном файле конфигурации, позволяет разработчику сфокусироваться на логике.