Pull to refresh

Конструктор форм в Yii

Yii *
Привет хабраюзеры!

В большинстве проектов на Yii которые я видел, работа с формами была организована самым простым способом, где рендеринг формы определялся в файле вида через виджет ActiveForm. Да, это безусловно оправдано для сложных форм, которые проблематично уместить в шаблон. Но сегодня я хочу поговорить о конструкторе форм и показать как это применяем мы.

Если вы плохо представляете, что такое конструктор форм, то прежде советую ознакомиться с соответствующим разделом документации www.yiiframework.ru/doc/guide/ru/form.builder.

Все просто. Работа с формами у нас построена по принципу «не смешивать модели формы и модели таблиц». А идея состоит в том, что бы в модель формы добавить функциональность конструктора форм.

Для этого создадим класс от которого будут наследоваться наши формы. Т.е. сделаем так, что бы модель формы сама определяла рендеринг исходя из конфигурации формы.

/**
 * FormModel.
 *
 * @author     Andrey Nilov <nilov@glavweb.ru>
 * @copyright  Copyright (c) 2007-2012 Glavweb.Soft, Russia. (http://glavweb.ru)
 */
class FormModel extends CFormModel
{
    /**
     * Config of the form
     * @var array
     */
    protected $_formConfig = array();

    /**
     * Config of the form by default
     * @var array
     */
    private $_defaultFormConfig = array(
        'method' => 'post'
    );
       
    /**
     * Class name of form
     * @var string
     */
    protected $_formClass = 'CForm';

    /**
     * Object of Form
     * @var Form
     */
    private $_form = null;    
    
    
    /**
     * Constructor
     * 
     * @param string $scenario Name of the scenario that this model is used in
     * @return void
     */
    public function __construct($scenario = '') 
    {
        parent::__construct($scenario);
        $this->_setFormConfig();
    }

    /**
     * Sets config of the form
     * 
     * @return void
     */
    private function _setFormConfig() 
    {
        $this->_formConfig = array_replace_recursive(
            $this->_defaultFormConfig, 
            $this->_formConfig()
        );
    }

    /**
     * Returns config of the form
     * 
     * @return array
     */
    protected function _formConfig() 
    {
        return $this->_formConfig;
    }
    
    /**
     * Returns the attribute labels
     *
     * @return array Attribute labels (name=>label)
     */
    public function attributeLabels()
    {
        return $this->getLabels();
    }    
    
    /**
     * Returns the config of the form
     * 
     * @return array
     */
    public function getFromConfig() 
    {
        return $this->_formConfig;
    }
    
    /**
     * Sets the config of the form
     * 
     * @param array $config
     * @return void
     */
    public function setFromConfig(array $config) 
    {
        $this->_formConfig = $config;
    }
    
    /**
     * Returns labels
     * 
     * @return array
     */
    public function getLabels() 
    {
        $labels = array();
        if (!empty($this->_formConfig['elements'])) {
            foreach ($this->_formConfig['elements'] as $name => $data) {
                if (isset($data['label'])) {
                    $labels[$name] = $data['label'];
                }
            }
        }
        
        return $labels;
    }
    
    /**
     * Returns object of Form
     * 
     * @return Form
     */
    public function getForm() 
    {
        if ($this->_form === null) {
            $this->_form = new $this->_formClass($this->_formConfig, $this);
        }
        
        return $this->_form;
    }    
}


Пройдемся по классу более подробно. Массив "$_formConfig" позволяет задать конфигурацию формы. Например:

/**
 * Config of the form
 * @var array
 */
protected $_formConfig = array(
    'activeForm' => array(
        'class'                  => 'CActiveForm',
        'id'                     => 'registration_form',
        'enableClientValidation' => true,
        'clientOptions' => array(
            'validateOnSubmit' => true
        )
    ),

    'elements' => array(
        ....
        'name' => array(
            'type' => 'text',
            'label' => 'ФИО'
        ),
        'organization' => array(
            'type' => 'text',
            'label' => 'Организация'
        ),
       ....
    ),

    'buttons' => array(
        'register' => array(
            'type'  => 'submit',
            'label' => 'Регистрация'
        )
    )
);

Так же для конфигурации формы можно переопределить метод "_formConfig()". Это удобно когда нужно во время конфигурации реализовать какую-то логику.

Свойство "$_formClass" позволяет изменить класс который используется для отображения формы (CForm или его потомок).

Заметьте, что теперь вам не обязательно указывать атрибуты формы. Для этого мы переопределили метод «attributeLabels()» и теперь атрибуты равны лейблам из конфигурации формы.

Получить объект формы (CForm) можно методом «getForm()».

Использование.

Рендеринг формы:
$formModel = new RegistrationForm();
$form = $formModel->getForm();
echo $form;


Использование совместно с моделью таблицы.
$user = new User();
$user->setAttributes($formModel->getAttributes());
$result = $user->save();


Настройка отображения формы. Для каждого элемента в "$_formConfig" мы можем указать свой шаблон с переопределив свойство «layout»:
protected $_formConfig = array(
    ....        
    'elements' => array(
        ....
        'name' => array(
            'type' => 'text',
            'label' => 'ФИО',
            'layout' => "{input}\n{label}\n{hint}\n{error}"
        ),
       ....
    ),


Автоматическое добавление двоеточия.

Одна из неприятностей которая встала при использовании конструктора форм, это невозможность автоматически добавлять двоеточие после заголовка поля. Если двоеточие вставить в конфиг формы в label, то оно будет видно и при отображении ошибок. Что бы исправить это, расширим классы «CForm» и «CFormInputElement» и чуть-чуть поправим наш «FormModel».

В «FormModel» добавляем:
    /**
     * Automatically add a colon in time rendering
     * @var boolean
     */
    public $autoAddColonForRender = true;

И меняем $_formClass
    /**
     * Class name of form
     * @var string
     */
    protected $_formClass = 'Form';


Создадим новый класс «Form» наследник «CForm», в нем переопределим свойство "$inputElementClass".
class Form extends CForm
{
    /**
     * The name of the class for representing a form input element.
     * @var string 
     */
    public $inputElementClass = 'FormInputElement';    
}


Создадим класс «FormInputElement» (наследник «CFormInputElement»), в нем переопределим метод «renderLabel()»
class FormInputElement extends CFormInputElement
{
    /**
     * @var string the layout used to render label, input, hint and error. They correspond to the placeholders
     * "{label}", "{input}", "{hint}" and "{error}".
     */
    public $layout = "{label}\n{input}\n{error}\n{hint}";
        
    /**
     * Automatically add a colon in time rendering
     * @var boolean
     */
    public $addColon = null;
    
    
    /**
     * Renders the label for this input.
     * 
     * @return string 
     */
    public function renderLabel() 
    {
        $model = $this->getParent()->getModel();
        
        $addColon = $this->addColon !== null ?
            $this->addColon :
            $model instanceof FormModel && $model->autoAddColonForRender;
        
        $label = $addColon ? $this->getLabel() . ':' : $this->getLabel();
        $options = array(
            'label'    => $label,
            'required' => $this->getRequired()
        );

        if (!empty($this->attributes['id'])) {
            $options['for'] = $this->attributes['id'];
        }

        return CHtml::activeLabel($this->getParent()->getModel(), $this->name, $options);
    }
}


Теперь используя свойство "$autoAddColonForRender" в «FormModel» возможно автоматически добавлять двоеточие после лейбла для всех элементов формы. Либо определять это для каждого элемента формы в отдельности во время конфиграции формы, установив соответствующее значение в «addColon».
   protected $_formConfig = array(
        ....        
        'elements' => array(
            ....
            'name' => array(
                'type' => 'text',
                'label' => 'ФИО',
                'addColon' => true
            ),
           ....
        ),


На этом сегодня всё, спасибо всем кто дочитал до конца. Объективная критика приветствуется.
Tags:
Hubs:
Total votes 14: ↑9 and ↓5 +4
Views 29K
Comments Comments 10