Використання будівника форм

При створенні HTML форм часто доводиться писати досить велику кількість повторюваного коду, який майже неможливо використовувати в інших проектах. Приміром, для кожного поля вводу нам необхідно вивести опис і можливі помилки валідації. Для того, щоб зробити можливим повторне використання подібного коду, можна використовувати будівник форм.

1. Загальна ідея

Будівник форм використовує об'єкт CForm для опису параметрів, необхідних для створення HTML форми, таких як моделі і поля, використовувані у формі, а також параметри побудови самої форми Розробнику достатньо створити об'єкт CForm, задати його параметри і викликати його метод для побудови форми.

Параметри форми організовані у вигляді ієрархії елементів форми. Коренем є об'єкт CForm. Кореневий об'єкт форми включає в себе дві колекції, які містять інші елементи: CForm::buttons та CForm::elements. Перша містить кнопки (такі як «Зберегти» або «Очистити»), друга - поля вводу, статичний текст і вкладені форми - об'єкти CForm, що знаходяться у колекції CForm::elements другої форми. Вкладена форма може мати свою модель даних і колекції CForm::buttons та CForm::elements.

Коли користувачі відправляють форму, дані, введені в поля вводу всієї ієрархії форми, включаючи вкладені форми, передаються на сервер. CForm включає у себе методи, що дозволяють автоматично привласнити дані полям відповідної моделі та провести валідацію.

2. Створення простої форми

Нижче буде показано, як побудувати форму входу на сайт.

Спочатку реалізуємо дію 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(), що повертає код форми.

3. Опис елементів форми

При використанні будівника форм, замість написання розмітки ми, головним чином, описуємо елементи форми. У цьому підрозділі ми опишемо, як задати властивість 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 реалізовані наступні типи полів вводу:

  • text
  • hidden
  • password
  • textarea
  • file
  • radio
  • checkbox
  • listbox
  • dropdownlist
  • checkboxlist
  • radiolist

Окремо слід описати використання "списочних" типів 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.

4. Доступ до елементів форми

Звертатися до елементів форми так само просто, як і до елементів масиву. Властивість 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'];

5. Створення вкладеної форми

Раніше ми вже описували вкладені форми. Форма в якій є вкладені форми називається головною. У даному розділі ми будемо використовувати форму реєстрації користувача як приклад створення вкладених форм, які відповідають декільком моделям даних. Далі дані для входу користувача зберігаються в моделі 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>

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

Головна перевага при використанні будівника форм - розділення логіки (конфігурація форми зберігається у окремому файлі) та представлення (метод 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">
$this->renderPartial('_form', array('form'=>$form));
</div>

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

які-небудь складні елементи інтерфейса
 
<?php echo $form['username']; ?>
 
які-небудь складні елементи інтерфейса
 
<?php echo $form['password']; ?>
 
які-небудь складні елементи інтерфейса

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

$Id: form.builder.txt 2890 2011-01-18 15:58:34Z qiang.xue $

Be the first person to leave a comment

Please to leave your comment.