Как визуализировать и добавлять подзаголовки в Backbone.js

У меня есть установка вложенных представлений, которая может быть несколько глубокой в ​​моем приложении. Есть несколько способов, которые я мог бы придумать для инициализации, рендеринга и добавления под-представлений, но мне интересно, что такое обычная практика.

Вот пара, о которой я подумал:

initialize : function () {

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template());

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

Плюсы: Вам не нужно беспокоиться о сохранении правильного порядка DOM с добавлением. Представления сначала инициализируются, поэтому в функции рендеринга не так много всего.

Минусы: Вы вынуждены повторно делегироватьEvents(), что может быть дорогостоящим? Функция рендеринга родительского представления загромождена всем рендерингом subview, который должен произойти? У вас нет возможности установить tagName элементов, поэтому шаблон должен поддерживать правильные имена тегов.

Другой способ:

initialize : function () {

},

render : function () {

    this.$el.empty();

    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});

    this.$el.append(this.subView1.render().el, this.subView2.render().el);
}

Плюсы: Вам не нужно повторно делегировать события. Вам не нужен шаблон, который содержит только пустые заполнители, и ваше tagName вернется к определению.

Минусы: Теперь вам нужно обязательно добавить вещи в правильном порядке. Родительский рендеринг представления по-прежнему загроможден для рендеринга subview.

С событием onRender:

initialize : function () {
    this.on('render', this.onRender);
    this.subView1 = new Subview({options});
    this.subView2 = new Subview({options});
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {

    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

Плюсы: Теперь логика subview отделена от метода view render().

С событием onRender:

initialize : function () {
    this.on('render', this.onRender);
},

render : function () {

    this.$el.html(this.template);

    //other stuff

    return this.trigger('render');
},

onRender : function () {
    this.subView1 = new Subview();
    this.subView2 = new Subview();
    this.subView1.setElement('.some-el').render();
    this.subView2.setElement('.some-el').render();
}

У меня есть разновидность и сочетается с кучей разных практик во всех этих примерах (так жаль, что), но какие из них вы бы сохранили или добавили? и что бы вы не сделали?

Краткое описание практики:

  • Мгновенное создание подпрограмм в initialize или в render?
  • Выполните всю логику рендеринга суб-представления в render или в onRender?
  • Используйте setElement или append/appendTo?

Ответ 1

Я обычно видел/использовал несколько различных решений:

Решение 1

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.inner = new InnerView();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.$el.append(this.inner.$el);
        this.inner.render();
    }
});

var InnerView = Backbone.View.extend({
    render: function() {
        this.$el.html(template);
        this.delegateEvents();
    }
});

Это похоже на ваш первый пример, с несколькими изменениями:

  • Порядок добавления дополнительных элементов
  • Внешний вид не содержит элементы html, которые должны быть установлены во внутреннем представлении (это означает, что вы все еще можете указать имя тега во внутреннем представлении).
  • render() называется ПОСЛЕ того, как элемент внутреннего представления был помещен в DOM, что полезно, если ваш внутренний вид render() метод размещает/подбирает себя на странице на основе позиции/размера других элементов (что общий пример использования, по моему опыту)

Решение 2

var OuterView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template); // or this.$el.empty() if you have no template
        this.inner = new InnerView();
        this.$el.append(this.inner.$el);
    }
});

var InnerView = Backbone.View.extend({
    initialize: function() {
        this.render();
    },

    render: function() {
        this.$el.html(template);
    }
});

Решение 2 может выглядеть более чистым, но это вызвало некоторые странные вещи в моем опыте и негативно повлияло на производительность.

Обычно я использую решение 1 по нескольким причинам:

  • Многие мои взгляды полагаются на уже находящиеся в DOM в их методе render()
  • При повторном рендеринге внешнего вида представления не нужно повторно инициализировать, что повторная инициализация может вызвать утечку памяти, а также вызвать уродливые проблемы с существующими привязками

Имейте в виду, что если вы инициализируете new View() каждый раз, когда вызывается render(), эта инициализация все равно будет вызывать delegateEvents(). Так что это не обязательно должно быть "con", как вы выразили.

Ответ 2

Это постоянная проблема с Backbone, и, по моему опыту, на самом деле нет удовлетворительного ответа на этот вопрос. Я разделяю ваше разочарование, тем более, что так мало указаний, несмотря на то, насколько распространен этот случай использования. Тем не менее, я обычно иду с чем-то похожим на ваш второй пример.

Прежде всего, я бы уволил из рук все, что требовало от вас делегирования событий. Магистральная модель просмотра с учетом событий является одним из наиболее важных компонентов, и потерять эту функциональность просто потому, что ваше приложение нетривиально, оставит плохой вкус в любом устье программиста. Так что царапины номер один.

Что касается третьего примера, я думаю, что это просто конец обычной практики рендеринга и не имеет большого значения. Возможно, если вы выполняете фактическое инициирование событий (т.е. Не надуманное событие "onRender" ), стоит просто привязать эти события к render. Если вы обнаружите, что render становится громоздким и сложным, у вас слишком мало облаков.

Вернемся к вашему второму примеру, который, вероятно, является меньшим из трех зол. Вот пример кода, снятого с Recipes With Backbone, который находится на странице 42 моего издания в формате PDF:

...
render: function() {
    $(this.el).html(this.template());
    this.addAll();
    return this;
},
  addAll: function() {
    this.collection.each(this.addOne);
},
  addOne: function(model) {
    view = new Views.Appointment({model: model});
    view.render();
    $(this.el).append(view.el);
    model.bind('remove', view.remove);
}

Это немного сложнее, чем ваш второй пример: они задают набор функций addAll и addOne, которые выполняют грязную работу. Я думаю, что этот подход работоспособен (и я, конечно, его использую); но он все еще оставляет странное послевкусие. (Простите все эти метафоры языка.)

К вашему моменту при добавлении в правильном порядке: если вы строго добавляете, убедитесь, что это ограничение. Но убедитесь, что вы рассматриваете все возможные схемы шаблонов. Возможно, вам действительно нужен элемент-заполнитель (например, пустой div или ul), который вы можете затем replaceWith новый (DOM), который содержит соответствующие подпункты. Добавление - это не единственное решение, и вы можете наверняка обойти проблему заказа, если вам это очень нравится, но я бы предположил, что у вас есть проблема с дизайном, если он отключает вас. Помните, subviews может иметь subviews, и они должны, если это уместно. Таким образом, у вас есть довольно древовидная структура, что довольно приятно: каждый subview добавляет все свои подсмотры, чтобы, до того, как родительский вид добавит другой, и т.д.

К сожалению, решение №2, вероятно, лучшее, что вы можете надеяться на использование готовой магистрали. Если вы заинтересованы в проверке сторонних библиотек, на которые я смотрел (но на самом деле еще не было времени играть с ними), Backbone.LayoutManager, который, по-видимому, имеет более здоровый метод добавления subviews. Тем не менее, даже у них были недавние дебаты по аналогичным проблемам с ними.

Ответ 3

Удивление это еще не упоминалось, но я бы серьезно подумал об использовании Marionette.

Он обеспечивает немного больше структуры для базовых приложений, включая конкретные типы просмотров (ListView, ItemView, Region и Layout), добавляя правильный Controller и многое другое.

Вот проект на Github и отличный руководство Адди Османи в книге "Основы основы" начните.

Ответ 4

У меня есть то, что я считаю, довольно всеобъемлющим решением этой проблемы. Это позволяет изменять модель внутри коллекции и отображать только ее представление (а не всю коллекцию). Он также обрабатывает удаление представлений зомби методами close().

var SubView = Backbone.View.extend({
    // tagName: must be implemented
    // className: must be implemented
    // template: must be implemented

    initialize: function() {
        this.model.on("change", this.render, this);
        this.model.on("close", this.close, this);
    },

    render: function(options) {
        console.log("rendering subview for",this.model.get("name"));
        var defaultOptions = {};
        options = typeof options === "object" ? $.extend(true, defaultOptions, options) : defaultOptions;
        this.$el.html(this.template({model: this.model.toJSON(), options: options})).fadeIn("fast");
        return this;
    },

    close: function() {
        console.log("closing subview for",this.model.get("name"));
        this.model.off("change", this.render, this);
        this.model.off("close", this.close, this);
        this.remove();
    }
});
var ViewCollection = Backbone.View.extend({
    // el: must be implemented
    // subViewClass: must be implemented

    initialize: function() {
        var self = this;
        self.collection.on("add", self.addSubView, self);
        self.collection.on("remove", self.removeSubView, self);
        self.collection.on("reset", self.reset, self);
        self.collection.on("closeAll", self.closeAll, self);
        self.collection.reset = function(models, options) {
            self.closeAll();
            Backbone.Collection.prototype.reset.call(this, models, options);
        };
        self.reset();
    },

    reset: function() {
        this.$el.empty();
        this.render();
    },

    render: function() {
        console.log("rendering viewcollection for",this.collection.models);
        var self = this;
        self.collection.each(function(model) {
            self.addSubView(model);
        });
        return self;
    },

    addSubView: function(model) {
        var sv = new this.subViewClass({model: model});
        this.$el.append(sv.render().el);
    },

    removeSubView: function(model) {
        model.trigger("close");
    },

    closeAll: function() {
        this.collection.each(function(model) {
            model.trigger("close");
        });
    }
});

Использование:

var PartView = SubView.extend({
    tagName: "tr",
    className: "part",
    template: _.template($("#part-row-template").html())
});

var PartListView = ViewCollection.extend({
    el: $("table#parts"),
    subViewClass: PartView
});

Ответ 5

Отметьте этот mixin для создания и рендеринга subviews:

https://github.com/rotundasoftware/backbone.subviews

Это минималистское решение, которое решает многие проблемы, обсуждаемые в этом потоке, включая порядок рендеринга, отсутствие необходимости делегирования событий и т.д. Обратите внимание, что случай представления коллекции (где каждая модель в коллекции представленный одним подзаголовком) - это другая тема. Лучшее общее решение, о котором я знаю, это CollectionView в Marionette.

Ответ 6

Мне не нравится какое-либо из вышеперечисленных решений. Я предпочитаю эту конфигурацию для каждого представления, чтобы вручную выполнять работу в методе рендеринга.

  • views может быть функцией или объектом, возвращающим объект определений вида
  • Когда вызывается родительский .remove, следует вызывать .remove вложенных детей из самого низкого порядка вверх (полностью из под-под-под-представлений)
  • По умолчанию родительский вид передает свою собственную модель и коллекцию, но параметры могут быть добавлены и переопределены.

Вот пример:

views: {
    '.js-toolbar-left': CancelBtnView, // shorthand
    '.js-toolbar-right': {
        view: DoneBtnView,
        append: true
    },
    '.js-notification': {
        view: Notification.View,
        options: function() { // Options passed when instantiating
            return {
                message: this.state.get('notificationMessage'),
                state: 'information'
            };
        }
    }
}

Ответ 7

Магистраль была намеренно построена так, чтобы не было "обычной" практики в отношении этого и многих других вопросов. Он должен быть максимально неприемлемым. Теоретически вам даже не нужно использовать шаблоны с Backbone. Вы можете использовать javascript/jquery в функции render представления, чтобы вручную изменить все данные в представлении. Чтобы сделать его более экстремальным, вам даже не нужна одна конкретная функция render. У вас может быть функция с именем renderFirstName, которая обновляет первое имя в dom и renderLastName, которое обновляет фамилию в dom. Если вы примете такой подход, было бы лучше с точки зрения производительности, и вам никогда не придется вручную делегировать события снова. Код также будет иметь общий смысл для кого-то, читающего его (хотя это будет более длинный/messier-код).

Однако, как правило, нет недостатка в использовании шаблонов и просто уничтожении и перестройке всего представления, и он рассматривает все вызовы рендеринга, так как даже допрашивающий ничего не делал. Так что большинство людей делают для почти каждой ситуации, с которой они сталкиваются. И то, почему упрямые структуры просто делают это по умолчанию.

Ответ 8

Вы также можете вставлять обработанные подзадачи в качестве переменных в основной шаблон в качестве переменных.

сначала визуализируйте subviews и преобразуйте их в html следующим образом:

var subview1 = $(subview1.render.el).html(); var subview2 = $(subview2.render.el).html();

(таким образом, вы могли бы также динамически строчить конкатенацию представлений типа subview1 + subview2 при использовании в циклах), а затем передать его в главный шаблон, который выглядит так: ... some header stuff ... <%= sub1 %> <%= sub2 %> ... some footer stuff ...

и введем его, наконец, следующим образом:

this.$el.html(_.template(MasterTemplate, { sub1: subview1, sub2: subview2 } ));

Относительно событий в подзонах: они, скорее всего, должны быть подключены в родительском (masterView) с помощью этого подхода, не входящего в subviews.