При работе с фреймворками всегда приходится создавать основной функционал самим, желательно, используя при этом возможности фреймворка (зачем тогда он нам нужен). Как понятно по заголовку речь пойдет про контекстно-зависимые формы в Yii. В статье описана реализация подобной формы, используя модальное окошко. Надеюсь, что кому то будет полезен именно такой вариант.
Забегая вперёд скажу, что в результате должно получиться вот такое поле, выпадающее меню и кнопка справа для выбора нужных данных.
Контекстно-зависимое окошко выбора:
После выбора:
Почему был выбран вариант с модальным окном? Это позволяет упростить выбор нужного элемента. В модальном окне можно выводить CGridView, при этом можно сортировать, искать и фильтровать данные, что очень удобно, если выбирать приходится из большого количества элементов.
Итак, ингредиенты для приготовления этой Yiiшницы:
Теперь, наберемся терпения и начнем приготовление.
Допустим, у нас есть контроллер menuController.php, view файл _form.php и модель Menu.php. Не буду останавливаться на том, кто есть кто, всё это стандартный, автоматически сгенерированный код Gii, Гы.
Сначала уточним, как работает модель. Будем считать, что у нас есть связка из типа данных и ID, которое привязано к типу данных. В модели (так же и в БД) должны присутствовать поля type и data_id, оба целочисленные. Для удобства при загрузке модели, будем загружать наименование объекта, на который он ссылается (понадобится при редактировании объекта). Допустим в поле data_name. Провернём нужные махинации в функции afterFind(). Так же сделаем список типов данных более динамичным. Определим статический массив в классе, таким образом, если нам нужно будет расширять или же сужать варианты типов, мы будем редактировать только класс модели.
Т.к. мы используем ActiveRecord то, по сути, необходимо знать только название модели и вьюшки для работы с конкретно взятым типом данных.
На этом рассмотрение модели закончим.
Смотрим view файл нашей формы _form.php
Собственно это весь файл формы. Основные элементы тут, это наше поле и функция для AJAX запроса данных.
Как вы можете видеть, функция получения данных, обращается к экшну menu/loadData. Посмотрим, что он делает:
Стоить отметить, что нужно установить для функции renderPartial параметр $processOutput = true, иначе не будут загружены скрипты, подключаемые виджетами в view файле.
Еще нужно уточнить очень важный момент, при установке параметра $processOutput, будут подгружены все файлы, включая и те, что уже были подключены на главной странице, что очень критично в случае, например, JQuery. Поэтому, советую установить расширение NLSClientScript, оно проконтролирует, чтобы все файлы подключались единожды.
View файлы надо хранить в предназначенной для этого папке контроллера, т.е. в нашем случае это будет /protected/views/menu
Теперь разберем один из view файлов для модального окошка. Можно использовать любой удобный вид для выбора данных, главное чтобы он работал через объект модели. Мне нравится CGridView, т.к. в нём есть все необходимое: поиск, пагинация и сортировка.
Тут ничего лишнего, один единственный грид с кнопками выбора элемента. В примере использован грид от bootstrap, но можно использовать стандартный CGridView.
Но тут есть одна хитрость. Как вы заметили, на кнопке выбора элемента висит невзрачный скрипт, выдирающий из таблицы id и title элемента. К сожалению, виджет GridView не позволяет оперировать данными отдельно взятой записи. Поэтому приходится таким вот образом получать нужные id и title, которые передаются функции selectData, описанной в view файле _form.php.
Вот собственно и всё. Для того чтобы добавить новый тип данных достаточно дописать еще одну строчку в массиве созданном в модели Menu.php и написать или сгенерировать, при помощи Gii, модель ActiveRecord для нового типа.
Забегая вперёд скажу, что в результате должно получиться вот такое поле, выпадающее меню и кнопка справа для выбора нужных данных.
Контекстно-зависимое окошко выбора:
После выбора:
Почему был выбран вариант с модальным окном? Это позволяет упростить выбор нужного элемента. В модальном окне можно выводить CGridView, при этом можно сортировать, искать и фильтровать данные, что очень удобно, если выбирать приходится из большого количества элементов.
Итак, ингредиенты для приготовления этой Yiiшницы:
- Одно Yii
- Расширение NLSClientScript
- Bootstrap по вкусу (расширение для 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 = $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 для нового типа.