I'm using backbone boilerplate to render my templates, its fetchTemplate method caches the rendered templates.
I would like to run some extra code on the rendered content, like initialize accordions, etc, but to do this with an async piled template is more tricky than I thought.
Here is an example:
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')
});
}
});
Beside the above I use a view handler as outlined at /
This I do something like
var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view)
this calls
$('#content').html(view.render().el)
But what happens is that when the template is not cached yet, render is called first, and postrender is called on time. On the other hand, when the template is already cached, then the template is rendered immediately, postrender gets called, but view.el
is not inserted in the DOM yet, thus $(this.el) is an empty list, and $('#duel-new').each() is "void".
Of course, I could add the postrender method after the viewHandler's render call, but this leads to the same problem, but on the first invocation of the render method. As the template is not piled yet, postrender gets called before its elements would exist, thus no handlers could be defined on these non-existing elements.
Any ideas on how to properly overe this problem? It's relatively straightforward for simple click events using .on for example, but what about more general structures, like $('#tabs').tabs()
for example?
My fetchTemplate function is the following:
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 = jadepile(contents,{other: "locals"});
JST[path] = tmpl;
return done(tmpl);
});
},
I'm using backbone boilerplate to render my templates, its fetchTemplate method caches the rendered templates.
I would like to run some extra code on the rendered content, like initialize accordions, etc, but to do this with an async piled template is more tricky than I thought.
Here is an example:
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')
});
}
});
Beside the above I use a view handler as outlined at http://lostechies./derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
This I do something like
var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view)
this calls
$('#content').html(view.render().el)
But what happens is that when the template is not cached yet, render is called first, and postrender is called on time. On the other hand, when the template is already cached, then the template is rendered immediately, postrender gets called, but view.el
is not inserted in the DOM yet, thus $(this.el) is an empty list, and $('#duel-new').each() is "void".
Of course, I could add the postrender method after the viewHandler's render call, but this leads to the same problem, but on the first invocation of the render method. As the template is not piled yet, postrender gets called before its elements would exist, thus no handlers could be defined on these non-existing elements.
Any ideas on how to properly overe this problem? It's relatively straightforward for simple click events using .on for example, but what about more general structures, like $('#tabs').tabs()
for example?
My fetchTemplate function is the following:
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.pile(contents,{other: "locals"});
JST[path] = tmpl;
return done(tmpl);
});
},
Share
Improve this question
edited Mar 5, 2015 at 20:45
Dan O
6,0902 gold badges34 silver badges52 bronze badges
asked May 1, 2012 at 14:17
AkashaAkasha
2,2121 gold badge31 silver badges51 bronze badges
2
- can you add the code for the fetchTemplate function? – Derick Bailey Commented May 1, 2012 at 17:45
-
Why is the view acting on elements outside its scope? There should be no
$("#some-id")
in your view, all the elements the view should act on should be underthis.el
orthis.el
itself. Also, can't you just do$('#content').html(view.el); view.render();
? – Esailija Commented May 7, 2012 at 20:18
4 Answers
Reset to default 5 +50There is no need for all these plications.
The original fetchTemplate
returns a jQuery promise. So should your version of it, if you don't know about jQuery's Deferreds and Promises it's a good time to look at them. Callbacks are dead ;)
Using promises, everything get as simple as:
In your initialize
do fetch the template and assign the promise. Then render only when the promise has been fulfilled, for example:
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...
});
}
});
Note that once the promise has been fulfilled, done
will always simply run immediately.
You can of course follow the same pattern if you modify your views $el
outside the view, i.e. wrap the code in view.templatedFetched.done(...)
.
I read the article to which you gave the link - the Zombies one. Nice it was and as far as I can see, it already contains the answer to your question, all that is needed is it to be searched. What I can think after reading and re-reading your question several times is that you may like to use the .NET way (as suggested in the Zombies article). That is, something like:
// 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();
In the view, you add an 'onAddToDom' method which will be called as and when your view is added to the dom. This can be used to call the postrender() method or you may rename postrender() to 'onAddToDom()'. This way, the problem is solved. How? Explanation follows.
You can redefine your view as:
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')
});
}
});
Now when you do something like
var view = Duel.Views.Home({model: mymodel})
viewHandler('#content').showView(view);
this gets called
$('#content').html(view.render().el)
if(view.onAddToDom)
view.onAddToDom();
which will call (what was previously known as) postrender() method.
Problem solved.
Warning: But mind well, this will fail (that is, onAddToDom -- or shall we call postrender()? -- won't be called ) if view.render()
is called directly and not from within viewHandler('selector').showView
since we call the onAddToDom from within that. But anyways, this is not needed, since if we wanted something to be called after rendering, we could add that to the render()
method itself. I just wanted to make sure there wasn't any confusion, so gave this warning.
can you try changing the marked lines:
view.$el
is the same as creating your own $(view.el)
, this is syntactic sugar in Backbone 0.9.0+
$('#duel-new')
scours over the whole dom-tree whereas $('#duel-new', this.$el)
only checks within the scope of your current view, largely reducing the amount of time spent on DOM traversal.
whilst this may not necessary fix your peculiar and particular issue, I've not had any issues myself with
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')
});
}
});
What version of Backbone are you using?
Are you using jQuery, zepto, or something else? What version?
The problem only happens when the template is already cached and not being retrieved asynchronously? If that's the case, can you create an example in a jsfiddle?
What browser(s) is the problem occurring in?
thus $(this.el) is an empty list, and $('#duel-new').each() is "void"
Please define exactly what you mean by "empty list" and "void". Unless something is seriously screwed up, with jQuery
$( this.el )
should be a jQuery object with length 1. With jQuery$( '#duel-new' ).each()
should be a jQuery object, possibly with length 0.As @20100 mentioned, if your Backbone version supports it you're better off using
this.$el
instead of$( this.el )
.this calls
$('#content').html(view.render().el)
jQuery.html()
is only documented as accepting a string argument, so I don't think this is a good idea if using jQuery.This I do something like
var view = Duel.Views.Home({model: mymodel}) viewHandler('#content').showView(view)
Shouldn't this be
new Duel.Views.Home( { model : mymodel } )
? Otherwise, inside the constructor,this
will beDuel.Views
.