Form Designer in Yii

    Hi habrayuzery!

    In most of the Yii projects that I saw, work with forms was organized in the simplest way, where the rendering of a form was defined in the view file through the ActiveForm widget. Yes, this is certainly justified for complex forms that are difficult to fit into a template. But today I want to talk about the form designer and show how we use it.

    If you have a poor idea of ​​what a form designer is, then I advise you first to familiarize yourself with the relevant section of the documentation www.yiiframework.ru/doc/guide/ru/form.builder .

    Everything is simple. Work with forms is built on the principle of "do not mix form models and table models." And the idea is to add form designer functionality to the form model.

    To do this, create a class from which our forms will be inherited. Those. let’s make the form model itself determine the rendering based on the configuration of the form.

    /**
     * FormModel.
     *
     * @author     Andrey Nilov 
     * @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;
        }    
    }
    


    Let's go through the class in more detail. Array "$ _formConfig" allows you to specify the configuration of the form. For instance:

    /**
     * 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' => 'Регистрация'
            )
        )
    );
    

    You can also override the "_formConfig ()" method to configure the form. This is convenient when you need to implement some kind of logic during configuration.

    The "$ _formClass" property allows you to change the class that is used to display the form (CForm or its descendant).

    Note that now you do not have to specify the attributes of the form. To do this, we redefined the attributeLabels () method and now the attributes are equal to the labels from the form configuration.

    You can get the form object (CForm) using the "getForm ()" method.

    Using.

    Form rendering:
    $formModel = new RegistrationForm();
    $form = $formModel->getForm();
    echo $form;
    


    Use in conjunction with a table model.
    $user = new User();
    $user->setAttributes($formModel->getAttributes());
    $result = $user->save();
    


    Customize the display of the form. For each element in "$ _formConfig" we can specify our template with overriding the "layout" property:
    protected $_formConfig = array(
        ....        
        'elements' => array(
            ....
            'name' => array(
                'type' => 'text',
                'label' => 'ФИО',
                'layout' => "{input}\n{label}\n{hint}\n{error}"
            ),
           ....
        ),
    


    Automatically add a colon.

    One of the troubles that arose when using the form designer was the inability to automatically add a colon after the field header. If the colon is inserted into the form config in the label, then it will be visible when errors are displayed. To fix this, we will extend the classes “CForm” and “CFormInputElement” and slightly correct our “FormModel”.

    In the "FormModel" add:
        /**
         * Automatically add a colon in time rendering
         * @var boolean
         */
        public $autoAddColonForRender = true;
    

    And change $ _formClass
        /**
         * Class name of form
         * @var string
         */
        protected $_formClass = 'Form';
    


    Let's create a new class “Form” and a successor to “CForm”, in it we will override the property "$ inputElementClass".
    class Form extends CForm
    {
        /**
         * The name of the class for representing a form input element.
         * @var string 
         */
        public $inputElementClass = 'FormInputElement';    
    }
    


    Let's create a class “FormInputElement” (successor of “CFormInputElement”), in it we will redefine the method “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);
        }
    }
    


    Now using the property "$ autoAddColonForRender" in "FormModel" it is possible to automatically add a colon after the label for all form elements. Or to define it for each element of the form separately during configuration of the form, having set the corresponding value in "addColon".
       protected $_formConfig = array(
            ....        
            'elements' => array(
                ....
                'name' => array(
                    'type' => 'text',
                    'label' => 'ФИО',
                    'addColon' => true
                ),
               ....
            ),
    


    That's all for today, thanks to everyone who read to the end. Objective criticism is welcome.

    Also popular now: