Как визуализировать и добавлять подзаголовки в 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.