0 follower

Использование конструктора форм

При создании 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">
<?php $this->renderPartial('_form', array('form'=>$form)); ?>
</div>

Если стандартный рендеринг формы не подходит (к примеру, в форме нужны уникальные декоративные элементы для определённых полей), в представлении можно поступить следующим образом:

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

В этом случае конструктор форм не очень эффективен, так как нам приходится описывать те же объёмы кода формы. Тем не менее, преимущество есть. Оно в том, что форма, описанная в отдельном файле конфигурации, позволяет разработчику сфокусироваться на логике.

Found a typo or you think this page needs improvement?
Edit it on github !