Использование конструктора форм =============================== При создании 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] какие-нибудь сложные элементы интерфейса какие-нибудь сложные элементы интерфейса какие-нибудь сложные элементы интерфейса ~~~ В этом случае конструктор форм не очень эффективен, так как нам приходится описывать те же объёмы кода формы. Тем не менее, преимущество есть. Оно в том, что форма, описанная в отдельном файле конфигурации, позволяет разработчику сфокусироваться на логике.