Отображение зависимых данных на примере каскадных выпадающих списков

в 18:11, , рубрики: backbone.js, javascript, метки:

Иногда попадаются интересные задачи, которые сложно написать с первого раза. Одной из таких задач оказались каскадные списки и легким комментарием я прикинул, что на Backbone у меня получится написать короче.

Короче не получилось… Отчасти из-за того, что я усложнил задачу до бесконечно выпадающих списков и из-за небольшой многословности Backbone.

Рисуем две окружности...

Немного подумав в голове появляется примерно такая блок-схема:
Отображение зависимых данных на примере каскадных выпадающих списков
При проектировании интерфейсов я всегда начинаю с самых мелкий деталей, в случае списка — это option и представление SelectItemView, которое его рендерит. Далее элементы образуют список, а списки образуют то, что нам нужно — базовое представление BaseView.

Задача стоит сделать бесконечно сложенные списки и для её решения можно применить что-то вроде связного списка для HTML:

<script type="text/template" id="base-tempate">
    <div class="primary"><!-- SelectView --></div>
    <div class="secondary"><!-- вложенный BaseView, если нужен --></div>
</script>

В контейнер .secondary будут рекурсивно вкладываться дочерние списки, а при изменении активного элемента .primary будет происходить очистка .secondary и рендер нового списка.

… И дорисовываем остальную сову

Спустя почти два часа я получил примерно такую демку. Код довольно простой и понятный, я не нашел того, что можно там долго объяснять.

Интересные моменты

1. Чтобы не вспоминать у каких элементов есть вложенный список, в конструкторе модели ItemModel происходит добавление значка ">" к метке:

    initialize: function(){
        if(this.get('items').length > 0){
            this.set('label', this.get('label') + ' >');
        }

2. После изменения выбранного элемента нужно найти модель, которая за него отвечает. Для решения задачи пришлось сделать массив itemViews представлений элемента, которые были отрендерины:

        changeItem: function(){
            u.each(this.itemViews, function(view){
                if(view.el === this.el.options[this.el.selectedIndex]){
                    this.collection.selectedModel = view.model;
                    this.collection.trigger('changeSelectedItem');
                }
            }, this);
        },

3. Вышеприведенный код посылает сигнал changeSelectedItem, который ловит BaseView и пытается отрендерить вторичный список, если есть нужная коллекция:

        renderSecondary: function(){
            var collection = this.collection.selectedModel.itemsCollection;
            var container = this.$el.find('.secondary');
            
            container.empty();
            
            if(collection)
                (new BaseView({ collection: collection })).render().$el.appendTo(container);
        }

Пояснения по коду

Весь код находиться в такой обёртке:

(function(j, b, u){
  // j - jQuery
  // b - Backbone
  // u - Underscope
})(jQuery.noConflict(), Backbone.noConflict(), _.noConflict());​

Обычно я пишу обертку чуть посложней, но в данном случае весь код находится в одном файле и этого достаточно. Заранее не прошу не критиковать сокращения, мне так удобней и читается проще, чем всякие доллары и нижние прочерки.

Заключение

Каждый раз, когда работаю с Backbone, радует аккуратность полученного кода. Хотя, возможно, это лишь для меня он получается аккуратный?

Автор: sdevalex

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js