Використання конструктора форм
==============================
При створенні 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]
які-небудь складні елементи інтерфейса
які-небудь складні елементи інтерфейса
які-небудь складні елементи інтерфейса
~~~
У цьому випадку конструктор форм не дуже ефективний, оскільки нам доводиться описувати ті ж обсяги коду форми. Проте, перевага є. Вона в тому, що форма, описана в окремому файлі конфігурації, дозволяє розробнику сфокусуватися на логіці.