0

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

mu is too short
  • 426,620
  • 70
  • 833
  • 800
user1271518
  • 636
  • 1
  • 9
  • 25

1 Answers1

2

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.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Thanks a lot for the input, so what is the best practice when it comes for fetching ? i saw that it's used in the initialize of the view (which will outcome in 2 render callings). – user1271518 Jul 24 '12 at 08:35
  • It also doesn't solve my problem, because again i will get 2 visits to the render function , one when there's no data and the other when there is, even if the fetch is called from the outside controller (app.js).. as you can see i'm calling from app.js: "var spots_view = new SpotsView({collection: spots});" which call the initialize method and fetchs the data and the next line is: "$('#spots').html(spots_view.render().el);" which should already have the data to display but doesnt – user1271518 Jul 24 '12 at 08:35
  • @user1271518: Why is it a problem that your `render` is called twice? Make your `render` smart enough that it won't matter. The best practice for `fetch` is to do what makes sense for your application and specific circumstances; sometimes it makes sense to put the rendering call in a `success` callback, sometimes it makes sense to make your `render` work with non-fetched or fetched models. – mu is too short Jul 24 '12 at 08:54
  • @ mu is too short: Because the 1st render call is called when "$('#spots').html(spots_view.render().el);" is called. Later on when the callback (of the fetch) returns and triggers the event, it's out of the "$('#spots').html(spots_view.render().el);" so it WON'T update the view. That ISN'T good. Doing: spots.fetch({success: function(){$('#spots').html(spots_view.render().el);}); <--- does solve the problem, but then it makes the fetch in the initialize useless and not sure about it as in best practice wise. See my point? – user1271518 Jul 24 '12 at 10:46
  • @user1271518: I see, you're doing `this.el = this.template();` and that will cause problems. I've added some notes about that. – mu is too short Jul 24 '12 at 17:14
  • That was it! your solution solved it. Inspite of it running render 3 times (according to the console), it updated the view as supposed to! Tnx a lot! learnt a lot from your notes. – user1271518 Jul 24 '12 at 19:14