Асинхронная загрузка шаблонов и действия после рендеринга с помощью Backbone
Я использую базовый шаблон для рендеринга своих шаблонов, его метод fetchTemplate кэширует визуализированные шаблоны.
Я хотел бы запустить некоторый дополнительный код для визуализированного содержимого, например, инициализировать аккордеоны и т. Д., Но сделать это с помощью асинхронного скомпилированного шаблона сложнее, чем я думал.
Вот пример:
Duel.Views.Home = Backbone.View.extend({
template: "/templates/duel_home.jade",
render: function() {
var view = this;
statusapp.fetchTemplate(this.template, function(tmpl) {
$(view.el).html( tmpl({duels: view.collection.toJSON()}) );
view.postrender();
});
return this;
},
postrender: function() {
$('#duel-new').each(function() {
console.log('Found something')
});
}
});
Помимо вышеизложенного, я использую обработчик представления, как описано на http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
Это я делаю что-то вроде
var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view)
это звонки
$('#content').html(view.render().el)
Но происходит то, что, когда шаблон еще не кэширован, сначала вызывается render, а postrender вызывается вовремя. С другой стороны, когда шаблон уже кэширован, шаблон отображается немедленно, вызывается postrender, но view.el
еще не вставлен в DOM, поэтому $(this.el) - пустой список, а $('#duel-new').each() - "void".
Конечно, я мог бы добавить метод postrender после вызова render viewHandler, но это приводит к той же проблеме, но при первом вызове метода render. Поскольку шаблон еще не скомпилирован, postrender вызывается до того, как его элементы будут существовать, поэтому никакие обработчики не могут быть определены для этих несуществующих элементов.
Есть идеи, как правильно преодолеть эту проблему? Это относительно просто для простых событий щелчка, например, на.on, но как насчет более общих структур, таких как $('#tabs').tabs()
например?
Моя функция fetchTemplate выглядит следующим образом:
fetchTemplate: function(path, done) {
window.JST = window.JST || {};
// Should be an instant synchronous way of getting the template, if it
// exists in the JST object.
if (JST[path]) {
return done(JST[path]);
}
// Fetch it asynchronously if not available from JST
return $.get(path, function(contents) {
var tmpl = jade.compile(contents,{other: "locals"});
JST[path] = tmpl;
return done(tmpl);
});
},
4 ответа
Нет необходимости во всех этих осложнениях.
Оригинал fetchTemplate
возвращает обещание jQuery Как и ваша версия, если вы не знаете об отложенных и обещанных jQuery, самое время взглянуть на них. Обратные вызовы мертвы;)
Используя обещания, все становится так просто, как: initialize
Получить шаблон и назначить обещание. Тогда рендеринг только тогда, когда обещание было выполнено, например:
Duel.Views.Home = Backbone.View.extend({
initialize: function () {
this.templateFetched = statusapp.fetchTemplate(this.template);
},
...
render: function () {
var view = this;
this.templateFetched.done(function (tmpl) {
view.$el.html( tmpl({duels: view.collection.toJSON()}) );
... // All your UI extras here...
});
}
});
Обратите внимание, что, как только обещание было выполнено, done
всегда будет просто бежать сразу. Конечно, вы можете следовать той же схеме, если вы измените свои взгляды $el
вне представления, т.е. обернуть код в view.templatedFetched.done(...)
,
Я прочитал статью, на которую вы дали ссылку - зомби. Хорошо, что это было, и, насколько я вижу, в нем уже есть ответ на ваш вопрос, все, что нужно, это его обыскать. Что я могу подумать после прочтения и перечитывания вашего вопроса несколько раз, так это то, что вы можете использовать способ.NET (как предложено в статье о зомби). То есть что-то вроде:
// in showView method of viewHandler
if (this.currentView) {
this.currentView.close();
}
this.currentView = view;
this.elem.html( this.currentView.render().el );
if ( this.currentView.onAddToDom ) // THIS IS THE IMPORTANT PART.
this.currentView.onAddToDom();
В представлении вы добавляете метод onAddToDom, который будет вызываться как и когда ваше представление добавляется в dom. Это может быть использовано для вызова метода postrender() или вы можете переименовать postrender() в onAddToDom(). Таким образом, проблема решена. Как? Объяснение следует.
Вы можете переопределить свою точку зрения как:
Duel.Views.Home = Backbone.View.extend({
template: "/templates/duel_home.jade",
render: function() {
var view = this;
statusapp.fetchTemplate(this.template, function(tmpl) {
$(view.el).html( tmpl({duels: view.collection.toJSON()}) );
});
return this;
},
onAddToDom: function() {
$('#duel-new').each(function() {
console.log('Found something')
});
}
});
Теперь, когда вы делаете что-то вроде
var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view);
это называется
$('#content').html(view.render().el)
if(view.onAddToDom)
view.onAddToDom();
который вызовет (ранее известный как) метод postrender().
Задача решена.
Предупреждение: но учтите, что это не удастся (то есть onAddToDom - или мы будем вызывать postrender()? - не будет вызвано), если view.render()
вызывается напрямую, а не изнутри viewHandler('selector').showView
так как мы вызываем onAddToDom из этого. Но в любом случае, это не нужно, так как, если мы хотим, чтобы что-то вызывалось после рендеринга, мы могли бы добавить это к render()
сам метод. Я просто хотел убедиться, что не было никакой путаницы, поэтому дал это предупреждение.
Можете ли вы попробовать изменить отмеченные строки:
view.$el
это то же самое, что создать свой собственный $(view.el)
Синтаксический сахар в Backbone 0.9.0+
$('#duel-new')
рыскает по всему дом-дереву, тогда как $('#duel-new', this.$el)
только проверки в рамках вашего текущего представления, что значительно сокращает время, затрачиваемое на обход DOM.
в то время как это, возможно, не обязательно исправляет Вашу специфическую и особую проблему, у меня не было никаких проблем с
Duel.Views.Home = Backbone.View.extend({
template: "/templates/duel_home.jade",
render: function() {
var view = this;
statusapp.fetchTemplate(this.template, function(tmpl) {
view.$el.html( tmpl({duels: view.collection.toJSON()}) ); // change this
view.postrender();
});
return this;
},
postrender: function() {
$('#duel-new', this.$el).each(function() { // change this
console.log('Found something')
});
}
});
Какую версию Backbone вы используете?
Вы используете jQuery, zepto или что-то еще? Какая версия?
Проблема возникает только тогда, когда шаблон уже кэшируется и не извлекается асинхронно? Если это так, можете ли вы создать пример в jsfiddle?
В каких браузерах возникает проблема?
таким образом, $(this.el) является пустым списком, а $('#duel-new').each() является "void"
Пожалуйста, определите, что именно вы подразумеваете под "пустым списком" и "пустым". Если что-то серьезно не облажалось, с jQuery
$( this.el )
должен быть объектом jQuery с длиной 1. С помощью jQuery$( '#duel-new' ).each()
должен быть объектом jQuery, возможно, с длиной 0.Как уже упоминалось @20100, если ваша версия Backbone поддерживает ее, вам лучше использовать
this.$el
вместо$( this.el )
,это звонки
$('#content').html(view.render().el)
jQuery.html()
задокументировано только как принятие строкового аргумента, поэтому я не думаю, что это хорошая идея, если использовать jQuery.Это я делаю что-то вроде
var view = Duel.Views.Home({model: mymodel}) viewHandler('#content').showView(view)
Разве это не должно быть
new Duel.Views.Home( { model : mymodel } )
? В противном случае внутри конструктораthis
будетDuel.Views
,