Плагин для Yii - YiiMultiBox
May 11, 2013 22:32 · 529 words · 3 minute read
Понадобилось мне сделать для автошколы небольшой виджет для удобного назначения учеников инструктору. Захотел я это сделать более-менее красиво - с помощью drag&drop: список нераспределённых учеников и списки инструкторов, в которые можно бросить ученика, и он автоматически назначится инструктору. К сожалению, ничего готового для Yii я не нашёл, так что пришлось писать самому :) Немного поплутав по интернету, нашёл решение на js. Мне оно приглянулось своей лаконичностью и законченностью, правда, коробок там всего 2, но это легко исправить.
Итак, js часть у меня есть, осталось связать её с виджетом Yii. Для начала унаследуемся от CJuiWidget:
class YiiMultiBox extends CJuiWidget {
Далее в методе init подключим js-скрипты (которые лежат в подкаталоге assets):
public function init() {
parent::init();
$this->registerScripts();
}
protected function registerScripts() {
parent::registerCoreScripts();
$basePath=Yii::getPathOfAlias('application.widget.yiimultibox.assets');
$baseUrl = Yii::app()->getAssetManager()->publish($basePath);
$cs=Yii::app()->getClientScript();
$cs->registerCssFile($baseUrl . DIRECTORY_SEPARATOR . 'lists.css');
$this->scriptUrl=$baseUrl;
$this->registerScriptFile('ui.yiimultibox.js');
$this->registerScriptFile('dragdrop.js');
$this->registerScriptFile('drag.js');
$this->registerScriptFile('coordinates.js');
В том же методе registerScripts инициализируем контейнеры, которые представляют собой обычные списки с заголовком (sic! тег lh ещё не стандартизирован):
foreach ($this->boxes as $name => $box) {
$header = '';
// заполним заголовок, если он указан
if (array_key_exists('header', $box)) {
$header = CHtml::tag('lh', array(), $box['header'], true);
}
// заполним список элементов
$elements = '';
foreach ($box['data'] as $key => $value) {
$content = $value['name'];
unset($value['name']);
$value['id'] = $key;
$elements .= CHtml::tag('li', $value, $content, true);
}
// выведем сам контейнер
echo CHtml::tag('ul', array("class" => "sortable boxy", "id" =>$name), $header.$elements, true);
}
Как видно по коду, в $this->boxes должна быть передана следующая структура:
array(
idBox1 => array(
'header' => 'Заголовок первого контейнера',
'data' => array(
idElement1 => array(
'name' => 'Название элемента контейнера',
'class' => 'someclass',
... и прочие html атрибуты
),
)
),
);
- idBox1 - id контейнера, по нему после сабмита формы можно будет получить
- header - опциональный элемент - видимый заголовок контейнера
- data - непосредственно элементы контейнера
- idElement1 - id элемента, который опять же пригодится после сабмита формы
- name - видимое имя элемента
- class и прочие html атрибуты не обязательны, но мне были нужны.
Остаток метода registerScripts:
echo CHtml::hiddenField("YiiMultiBox", "", array('id' => "YiiMultiBox"));
Yii::app()->clientScript->registerScript(
'EMultiSelect',
'window.onload = function() {
makeDraggable();
attachToForm("'.$this->form.'");
};',
CClientScript::POS_READY
);
}
Первой строчкой я добавляю скрытое поле, в которое будет сериализоваться изменённое состояние контейнеров, потом регистрирую обработчики для оживления картины.
Функция makeDraggable служит для активации drag&drop'а между контейнерами:
function makeDraggable() {
var list = document.getElementsByClassName("boxy");
for (var i = 0; i < list.length; i++) {
DragDrop.makeListContainer(list[i]);
list[i].onDragOver = function() { this.style["background"] = "#EEF"; };
list[i].onDragOut = function() {this.style["background"] = "none"; };
}
}
attachToForm несколько сложнее и предназначена для обработки отправки формы. Функция навешивает обработчик и при сабмите сериализует состояние контейнеров в скрытое поле:
function attachToForm(name) {
$("#"+name).submit(function(eventObject) {
var result = {};
$.each($("ul.boxy"), function(index, element) {
var box = [];
$(this).find("li").each(function() {
box.push($(this).attr("id"));
});
result[element.id] = box;
});
document.getElementById("YiiMultiBox").value = JSON.stringify(result);
return true;
});
}
Итак, виджет готов! Осталось его подключить:
$form->widget('application.widget.yiimultibox.YiiMultiBox', array(
'form' => 'instructors-form',
'boxes' => $students));
А после POST-запроса обработать изменения:
$instructors = json_decode($_POST['YiiMultiBox']);
foreach ($instructors as $instructor => $students) {
foreach ($students as $student) {
Students::model()->updateByPk($student, array('id_instructor'=> $instructor));
}
}
На этом, пожалуй, и всё. Как видите, написание своего плагина для Yii не такая уж и сложная задача. Буду рад, если кому пригодится :) Ах да, все файлы можно взять на github.