Використання конструктора форм
==============================

При створенні 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]
<h1>Вхід</h1>

<div class="form">
<?php echo $form; ?>
</div>
~~~

> 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,
        ),

        '<hr />',

        '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]
<h1>Реєстрація</h1>

<div class="form">
<?php echo $form; ?>
</div>
~~~


Свій рендеринг форми
--------------------

Головна перевага при використанні конструктора форм - розділення логіки (конфігурація форми зберігається у окремому файлі) та представлення (метод [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]
<?php
echo $form->renderBegin();

foreach($form->getElements() as $element)
	echo $element->render();

echo $form->renderEnd();
~~~

Для цього достатньо:

~~~
[php]
<div class="form">
<?php $this->renderPartial('_form', array('form'=>$form)); ?>
</div>
~~~

Якщо стандартний рендеринг форми не підходить (наприклад, у формі потрібні унікальні декоративні елементи для певних полів), у відображенні можна вчинити наступним чином:

~~~
[php]
які-небудь складні елементи інтерфейса

<?php echo $form['username']; ?>

які-небудь складні елементи інтерфейса

<?php echo $form['password']; ?>

які-небудь складні елементи інтерфейса
~~~

У цьому випадку конструктор форм не дуже ефективний, оскільки нам доводиться описувати ті ж обсяги коду форми. Проте, перевага є. Вона в тому, що форма, описана в окремому файлі конфігурації, дозволяє розробнику сфокусуватися на логіці.
