При створенні HTML форм часто доводиться писати досить велику кількість повторюваного коду, який майже неможливо використовувати в інших проектах. Приміром, для кожного поля вводу нам необхідно вивести опис і можливі помилки валідації. Для того, щоб зробити можливим повторне використання подібного коду, можна використовувати конструктор форм.
Конструктор форм використовує обʼєкт CForm для опису параметрів, необхідних для створення HTML форми, таких як моделі і поля, використовувані у формі, а також параметри побудови самої форми. Розробнику достатньо створити обʼєкт CForm, задати його параметри і викликати його метод для побудови форми.
Параметри форми організовані у вигляді ієрархії елементів форми. Коренем є обʼєкт CForm. Кореневий обʼєкт форми включає в себе дві колекції, які містять інші елементи: CForm::buttons та CForm::elements. Перша містить кнопки (такі як «Зберегти» або «Очистити»), друга - поля вводу, статичний текст і вкладені форми - обʼєкти CForm, що знаходяться у колекції CForm::elements другої форми. Вкладена форма може мати свою модель даних і колекції CForm::buttons та CForm::elements.
Коли користувачі відправляють форму, дані, введені в поля вводу всієї ієрархії форми, включаючи вкладені форми, передаються на сервер. CForm включає у себе методи, що дозволяють автоматично привласнити дані полям відповідної моделі та провести валідацію.
Нижче буде показано, як побудувати форму входу на сайт.
Спочатку реалізуємо дію login
:
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, як описано у розділі «Створення моделі», використовує модель LoginForm
.
Якщо форма відправлена і всі вхідні дані пройшли перевірку без помилок,
перенаправляємо користувача на сторінку site/index
. Інакше, виводимо представлення login
, яке описує форму.
Псевдонім шляху application.views.site.loginForm
вказує на файл PHP
protected/views/site/loginForm.php
. Цей файл повертає масив, що описує налаштування, необхідні для CForm:
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
:
<h1>Вхід</h1>
<div class="form">
<?php echo $form; ?>
</div>
Підказка: Наведений вище код
echo $form;
еквівалентнийecho $form->render();
. Використання більш компактного запису можливо, так як CForm реалізує магічний метод__toString
, у якому викликається методrender()
, що повертає код форми.
При використанні конструктора форм, замість написання розмітки ми, головним чином, описуємо елементи форми. У цьому підрозділі ми опишемо як задати властивість CForm::elements. Ми не будемо описувати CForm::buttons, так як конфігурація цієї властивості практично нічим не відрізняється від CForm::elements.
Властивість CForm::elements є масивом, кожен елемент якого відповідає елементу форми. Це може бути поле вводу, статичний текст або вкладена форма.
Поле вводу, головним чином, складається з заголовка, самого поля, підказки і тексту помилки і повинно відповідати певному атрибуту моделі. Опис поля вводу міститься в екземплярі класу CFormInputElement. Наведений нижче код масиву CForm::elements описує одне поле вводу:
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
Тут вказано, що атрибут моделі називається username
, тип поля - text
і його атрибут maxlength
дорівнює 32.
Будь-яка доступна для запису властивість CFormInputElement може бути налаштована наведеним вище способом. Наприклад, можна задати властивість hint для того, щоб показувати підказку або властивість items, якщо поле є випадаючим списком або групою елементів checkbox або radio. Якщо імʼя опції не є властивістю CFormInputElement, воно буде вважатися атрибутом відповідного HTML-тегу input. Приміром, так як вище опція maxlength
не є властивістю CFormInputElement, вона буде використана як атрибут maxlength
HTML-елемента input.
Слід окремо зупинитися на властивості type.
Вона визначає тип поля вводу. Наприклад, тип text
означає, що буде використаний елемент форми input
, а password
- поле для вводу пароля. В CFormInputElement реалізовані наступні типи полів вводу:
Окремо слід описати використання типів списку dropdownlist
, checkboxlist
та radiolist
. Для них необхідно задати властивість items відповідного елемента input. Зробити це можна так:
'gender'=>array(
'type'=>'dropdownlist',
'items'=>User::model()->getGenderOptions(),
'prompt'=>'Оберіть значення:',
),
…
class User extends CActiveRecord
{
public function getGenderOptions()
{
return array(
0 => 'Чоловік',
1 => 'Жінка',
);
}
}
Даний код згенерує випадаючий список з текстом «Оберіть значення:» і опціями «Чоловік» та «Жінка», які ми отримуємо із методу getGenderOptions
моделі User
.
Крім даних типів полів, у властивості type можна вказати клас або псевдонім шляху віджету. Клас віджету повинен успадковувати CInputWidget або CJuiInputWidget. При генерації елементу форми, буде створено і виконано екземпляр класу віджету. Віджет буде використовувати конфігурацію, передану через налаштування елемента форми.
Досить часто у формі, крім полів вводу, міститься деяка декоративна HTML розмітка. Приміром, горизонтальний розділювач для виділення певних частин форми або зображення, що покращує зовнішній вигляд форми. Подібний HTML код можна описати в колекції CForm::elements як статичний текст. Для цього у CForm::elements у потрібному нам місці замість масиву необхідно використовувати рядок. Наприклад:
return array(
'elements'=>array(
......
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'<hr />',
'rememberMe'=>array(
'type'=>'checkbox',
)
),
......
);
У наведеному коді ми вставили горизонтальний розділювач між полями password
і rememberMe
.
Статичний текст найкраще використовувати в тому випадку, коли розмітка та її розташування досить унікальні. Якщо деяку розмітку повинен містити кожен елемент форми, найкраще перевизначити безпосередньо побудову розмітки форми, як буде описано далі.
Вкладені форми використовуються для розділення складних форм на декілька повʼязаних простих. Приміром, ми можемо розділити форму реєстрації користувача на дві вкладені форми: дані для входу і дані профілю. Кожна вкладена форма може, хоча і не зобовʼязана, бути повʼязана з моделлю даних. У прикладі з формою реєстрації, якщо ми зберігаємо дані для входу і дані профілю в двох різних таблицях (і відповідно в двох моделях), то кожна вкладена форма буде зіставлена відповідній моделі даних. Якщо ж всі дані зберігаються в одній таблиці, жодна із вкладених форм не буде привʼязана до моделі і обидві будуть використовувати модель головної форми.
Вкладена форма, як і головна, описується обʼєктом CForm. Для того, щоб описати вкладену форму, необхідно визначити елемент типу form
у властивості CForm::elements:
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
із прикладу, можна використовувати наступний код:
$username = $form->elements['username'];
Для доступу до елемента email
форми реєстрації користувача з прикладу, можна використовувати:
$email = $form->elements['user']->elements['email'];
Так як CForm реалізує доступ до елементів CForm::elements як до масиву, можна спростити код до:
$username = $form['username'];
$email = $form['user']['email'];
Раніше ми вже описували вкладені форми. Форма в якій є вкладені форми називається головною. У даному розділі ми будемо використовувати форму реєстрації користувача як приклад створення вкладених форм, які відповідають декільком моделям даних. Далі дані для входу користувача зберігаються в моделі User
, а дані профілю - в моделі Profile
.
Реалізуємо дію register
наступним чином:
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
:
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
:
<h1>Реєстрація</h1>
<div class="form">
<?php echo $form; ?>
</div>
Головна перевага при використанні конструктора форм - розділення логіки (конфігурація форми зберігається у окремому файлі) та представлення (метод CForm::render). В результаті, ми можемо налаштувати рендеринг форми або перевизначенням методу CForm::render, або своїм представленням. Обидва варіанти дозволяють не змінювати конфігурацію і дозволяють використовувати її повторно.
При перевизначенні CForm::render, необхідно, головним чином, обійти колекції CForm::elements і CForm::buttons і викликати метод CFormElement::render для кожного елементу. Наприклад:
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
echo $form->renderBegin();
foreach($form->getElements() as $element)
echo $element->render();
echo $form->renderEnd();
Для цього достатньо:
<div class="form">
<?php $this->renderPartial('_form', array('form'=>$form)); ?>
</div>
Якщо стандартний рендеринг форми не підходить (наприклад, у формі потрібні унікальні декоративні елементи для певних полів), у відображенні можна вчинити наступним чином:
які-небудь складні елементи інтерфейса
<?php echo $form['username']; ?>
які-небудь складні елементи інтерфейса
echo $form['password']; ?>
які-небудь складні елементи інтерфейса
У цьому випадку конструктор форм не дуже ефективний, оскільки нам доводиться описувати ті ж обсяги коду форми. Проте, перевага є. Вона в тому, що форма, описана в окремому файлі конфігурації, дозволяє розробнику сфокусуватися на логіці.