backbone.js view doesn't display results because of asynchronous fetch , doesn't render
-
22-06-2021 - |
Question
I've noticed that my view's render function is being called 2 times. Here's my code:
the View, which get's a collection:
define([
'jquery',
'underscore',
'backbone',
'mustache',
'icanhaz',
'views/spots/Spot',
'collections/Spots',
'text!../../../../templates/spots/spots.mustache!strip',
], function($,
_,
Backbone,
mustache,
ich,
SpotView,
Spots,
SpotsTemplate){
var SpotsView = Backbone.View.extend({
initialize: function(){
var ich = window['ich'],
spots = ich.addTemplate('spots',SpotsTemplate);
spots = ich['spots'];
this.template = spots;
_.bindAll(this,'render');
var self = this;
this.collection.bind("all", function() { self.render(); }, this);
this.collection.fetch();
},
events: {
"change": "render"
},
render: function(){
window.counter = window.counter +1;
console.log('inside render for the ' + window.counter + ' times!');
this.el = this.template();
this.collection.each(function (spot) {
$(this.el).append(new SpotView({model:spot}).render().el);
}, this);
console.log($(this.el).children().length);
return this;
}
});
// Returning instantiated views can be quite useful for having "state"
return SpotsView;
});
the code inside app.js , when i try to display
var spots = new Spots({model: Spot});
window.counter = 0 + 0;
var spots_view = new SpotsView({collection: spots});
$('#spots').html(spots_view.render().el);
My output is:
inside render for the 1 times!
1
inside render for the 2 times!
6
while playing with different things ive noticed it is being called 3 times even. What am i doing wrong? obviously by the time the results are brought from the server to the render function this line:
$('#spots').html(spots_view.render().el);
has already passed
thanks a lot
Solution
Your view's initialize
says this:
this.collection.bind("all", function() { self.render(); }, this);
this.collection.fetch();
and fetch
will reset the collection:
When the model data returns from the server, the collection will reset.
Resetting the collection will:
[trigger] a single "reset" event at the end
By binding to "all"
, any event on the collection will trigger a render
call. So your view will render once when you explicitly say spots_view.render()
and again when the fetch
call gets something back from the server.
As an aside, you have this:
_.bindAll(this,'render');
so you don't need to use self
and self.render()
or supply the context argument to bind
, you could simply say this:
_.bindAll(this, 'render');
this.collection.bind("all", this.render);
You're also doing this in your render
:
this.el = this.template();
and that's never a good idea. You should be using setElement
if you need to change your view's this.el
; that will take care of rebinding the events and updating this.$el
. However, that won't help you if you've already put this.el
into the DOM. Instead of replacing el
entirely, you should put everything you need inside this.el
:
var $content = $(this.template());
this.collection.each(function (spot) {
var spot = new SpotView({ model: spot });
$content.append(spot.render().el);
});
this.$el.html($content);
Then you can empty it and re-render it in response to events without any problems.