Контекстно-зависимая форма в Yii

от автора

При работе с фреймворками всегда приходится создавать основной функционал самим, желательно, используя при этом возможности фреймворка (зачем тогда он нам нужен). Как понятно по заголовку речь пойдет про контекстно-зависимые формы в Yii. В статье описана реализация подобной формы, используя модальное окошко. Надеюсь, что кому то будет полезен именно такой вариант.

Забегая вперёд скажу, что в результате должно получиться вот такое поле, выпадающее меню и кнопка справа для выбора нужных данных.



Контекстно-зависимое окошко выбора:

После выбора:

Почему был выбран вариант с модальным окном? Это позволяет упростить выбор нужного элемента. В модальном окне можно выводить CGridView, при этом можно сортировать, искать и фильтровать данные, что очень удобно, если выбирать приходится из большого количества элементов.
Итак, ингредиенты для приготовления этой Yiiшницы:

Теперь, наберемся терпения и начнем приготовление.

Допустим, у нас есть контроллер menuController.php, view файл _form.php и модель Menu.php. Не буду останавливаться на том, кто есть кто, всё это стандартный, автоматически сгенерированный код Gii, Гы.

Модель (Menu.php)

Сначала уточним, как работает модель. Будем считать, что у нас есть связка из типа данных и ID, которое привязано к типу данных. В модели (так же и в БД) должны присутствовать поля type и data_id, оба целочисленные. Для удобства при загрузке модели, будем загружать наименование объекта, на который он ссылается (понадобится при редактировании объекта). Допустим в поле data_name. Провернём нужные махинации в функции afterFind(). Так же сделаем список типов данных более динамичным. Определим статический массив в классе, таким образом, если нам нужно будет расширять или же сужать варианты типов, мы будем редактировать только класс модели.
Т.к. мы используем ActiveRecord то, по сути, необходимо знать только название модели и вьюшки для работы с конкретно взятым типом данных.

    static $types = array(         1 => array("name" => "Страница",    "model" => "Pages", "view" => "pages_grid"),         2 => array("name" => "Документ",    "model" => "Docs",  "view" => "docs_grid"),         3 => array("name" => "Папка",       "model" => "Cats",  "view" => "cats_grid"),     );      public static function getSimpleTypes() { // возвращает список типов. Нужен для DropDownList         $st = array();         foreach (Menu::$types as $key => $value)             $st[$key] = $value["name"];         return $st;     }          var $data_name;     public function afterFind() {         $dataModel = Menu::$types[$this->type]['model']::model()->findByPk($this->data_id);         $this->data_name = $dataModel->title; // Присваиваем переменной $data_name название выбранного элемента         parent::afterFind();     } 

На этом рассмотрение модели закончим.

View формы (_form.php)

Смотрим view файл нашей формы _form.php

<!-- Создаем стандартную форму -->  <?php $form=$this->beginWidget('bootstrap.widgets.TbActiveForm',array(         'id'=>'menu-form',         'enableAjaxValidation'=>false,         'type' => 'horizontal', )); ?> <!-- Здесь могут быть Ваши поля формы -->              <?php echo $form->errorSummary($model);?>     <?php echo $form->textFieldRow($model,'name');?>  <!-- Скрытое поле, в котором лежит data_id -->       <?php echo $form->textField($model,'data_id',array('class'=>'hide')); ?>  <!-- Сами пишем обертку будущего поля согласно правилам bootstrap-->       <div class="control-group">         <div class="control-label">             <?=$form->labelEx($model,'type')?>         </div>         <div class="controls">             <div class="input-append">                 <?php echo $form->dropDownList($model,'type',Menu::getSimpleTypes());?>                 <button class="btn" id="data-select-btn" data-loading-text="..." type="button"><i class="icon-list"></i></button>             </div>         </div>     </div>  <!-- Уведомление о том, что элемент был выбран. Крайне необходим для действия Update, чтобы было видно, что выбрано-->               <div id="data-info" class="alert alert-success controls <?if($model->isNewRecord):?>hide<?endif;?>">         <i class="icon-file"></i>         <span class="info">             <?if(!$model->isNewRecord) echo $model->data_name?>         </span>     </div>  <!-- Кнопка создания/сохранения-->           <div class="form-actions">         <?php $this->widget('bootstrap.widgets.TbButton', array(                 'buttonType'=>'submit',                 'type'=>'primary',                 'label'=>$model->isNewRecord ? 'Create' : 'Save',         )); ?>     </div>  <?php $this->endWidget(); ?>  <!-- Модальное окошко для выбора нужного материала-->       <?php $this->beginWidget('bootstrap.widgets.TbModal', array('id'=>'dataModal')); ?>     <div class="modal-header">         <a class="close" data-dismiss="modal">×</a>         <h4><?=Yii::t("menu", "Выберите материал")?></h4>     </div>     <div class="modal-body"></div>     <div class="modal-footer">         <?php $this->widget('bootstrap.widgets.TbButton', array(             'label'=>Yii::t("menu", "Отмена"),             'url'=>'#',             'htmlOptions'=>array('data-dismiss'=>'modal'),         )); ?>     </div> <?php $this->endWidget(); ?>          <script> // Функция для вызова из модального окошка     function selectData(id, name) {         $("#Menu_data_id").val(id);         $("#data-info .info").html(name);         $("#data-info").show();         $('#dataModal').modal("hide");     }  // Обнуляем data_id если меняем тип данных         $('#Menu_type').change(function(){         $("#Menu_data_id").val("");         $("#data-info").hide();     })  // Функция которая показывает модальное окно с данными для выбора, полученными через AJAX     $('#data-select-btn').click(function(){         var buttn = this;          $(buttn).button('loading');         $.ajax({           url: "<?php echo $this->createAbsoluteUrl('menu/loadData') ?>",           cache: false,           data: "type="+$("#Menu_type").val(),           success: function(html){             $(".modal-body").html(html);                    $(buttn).button('reset');             $('#dataModal').modal().css({                 width: 'auto',                 'margin-left': function () {                     return -($(this).width() / 2);                 },             });           }                    });     }) </script> 

Собственно это весь файл формы. Основные элементы тут, это наше поле и функция для AJAX запроса данных.
Как вы можете видеть, функция получения данных, обращается к экшну menu/loadData. Посмотрим, что он делает:

Контроллер (menuController.php)

    public function actionLoadData($type)     {         $model_name = Menu::$types[$type]['model'];          $model = new $model_name('search'); // создаем модель данных нужного типа          if(isset($_GET[$model_name])) // чтобы работали функции поиска нужно передать параметры в модель                 $model->attributes=$_GET[$model_name];          $this->renderPartial(Menu::$types[$type]['view'],array(                 'model'=>$model,         ), false, true); // обязательно ставим $processOutput = true, чтобы работали скрипты в модальном окошке.     } 

Стоить отметить, что нужно установить для функции renderPartial параметр $processOutput = true, иначе не будут загружены скрипты, подключаемые виджетами в view файле.
Еще нужно уточнить очень важный момент, при установке параметра $processOutput, будут подгружены все файлы, включая и те, что уже были подключены на главной странице, что очень критично в случае, например, JQuery. Поэтому, советую установить расширение NLSClientScript, оно проконтролирует, чтобы все файлы подключались единожды.

View файл для модального окошка (docs_grid.php)

View файлы надо хранить в предназначенной для этого папке контроллера, т.е. в нашем случае это будет /protected/views/menu
Теперь разберем один из view файлов для модального окошка. Можно использовать любой удобный вид для выбора данных, главное чтобы он работал через объект модели. Мне нравится CGridView, т.к. в нём есть все необходимое: поиск, пагинация и сортировка.

$this->widget('bootstrap.widgets.TbGridView',array( 	'id'=>'docs-grid', 	'dataProvider'=>$model->search(), 	'filter'=>$model, 	'columns'=>array(             'id',             'title',             'updated',             array(                 'class'=>'CButtonColumn',                 'template' => "{insert}",                 'buttons' => array(                     "insert" => array(                         'label' => "выбрать",                         'options' => array(                             "class" => "btn btn-mini btn-success",                             "onclick" => 'selectData($(this).parent().parent().children(":nth-child(1)").text(),$(this).parent().parent().children(":nth-child(2)").text());',                         )                     ),                 )             ), 	), )); 

Тут ничего лишнего, один единственный грид с кнопками выбора элемента. В примере использован грид от bootstrap, но можно использовать стандартный CGridView.
Но тут есть одна хитрость. Как вы заметили, на кнопке выбора элемента висит невзрачный скрипт, выдирающий из таблицы id и title элемента. К сожалению, виджет GridView не позволяет оперировать данными отдельно взятой записи. Поэтому приходится таким вот образом получать нужные id и title, которые передаются функции selectData, описанной в view файле _form.php.

Вот собственно и всё. Для того чтобы добавить новый тип данных достаточно дописать еще одну строчку в массиве созданном в модели Menu.php и написать или сгенерировать, при помощи Gii, модель ActiveRecord для нового типа.

ссылка на оригинал статьи http://habrahabr.ru/post/178303/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *