Плагин для Yii - YiiMultiBox

May 11, 2013 22:32 · 529 words · 3 minute read php

Демо Понадобилось мне сделать для автошколы небольшой виджет для удобного назначения учеников инструктору. Захотел я это сделать более-менее красиво - с помощью 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.