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