0

For some reason, I can't get this to correctly be passed to the callback to render the view. I've tried _.bind and _.bindAll methods, but no matter which way I use to pass context, I always end up with a different this in render than I had in initialize

Any help is appreciated

  Navigation.Collection = Backbone.Collection.extend({
    model : Navigation.Model,
    comparator : function(item) {
      return item.get("orderId");
    }
  });

  Menu = new Navigation.Collection();

  Navigation.Views.List = Backbone.View.extend({
    el : 'nav',
    tagName : "div",
    className : "navigation",
    collection : Menu,
    initialize : function(e) {
      console.log(this);
      this.template = "navigation/list";
      this.settings = Settings;

      this.collection.on("add", this.render, this);
    },
    render: function() {
      console.log(this);

      var renderedContent = this.template(this.collection.toJSON());
      console.log(renderedContent);

      return this;
    },

Example with bind:

initialize : function(e) {
  this.collection.on("add", _.bind(this.render, this));
}

Example with bindAll:

initialize : function(e) {
  _.bindAll(this, "render");
  this.collection.on("add", this.render);
}

Console output

First print:

child {cid: "view2", options: Object, views: Object, __manager__: Object, _removeViews: function…}

Second print:

Object {resolve: function, resolveWith: function, reject: function, rejectWith: function, notify: function…}

EDIT: Adding where 'render' is called. This is immediately after Navigation.View.List(seen above) is defined in the code.

Navigation.registerModule = function(data) {
  _.extend(data, {
    id : Math.random()
  });
  Menu.add(new Navigation.Model(data));
  Navigation.LayoutManager.removeView(true);
  Navigation.cachedRendering = null;
};
Navigation.View = new Navigation.Views.List();

Navigation.LayoutManager = new Backbone.Layout({
  views : {
    nav : Navigation.View
  }
});

Navigation.LayoutManager.$el.appendTo("nav");

Navigation.LayoutManager.render();

return Navigation;

EDIT: In case anyone else stumbles across the same thing, the solution is to use beforeRender and afterRender. The confusion happened because I was upgrading dependencies and the old version of backbone.layoutmanager did not have these two helpers, and render() was used instead - with a manage parameter to access the after state.

tsjnsn
  • 441
  • 5
  • 12
  • 1
    Can you post examples of how you tried to use `bind` and `bindAll`? If used properly either one should absolutely be able to solve your issue. – machineghost Jul 24 '13 at 15:49
  • `this.collection.on("add", _.bind(this.render, this));` and `_.bindAll(this, "render");` with `this.collection.on("add", this.render)` – tsjnsn Jul 24 '13 at 16:48
  • So what is this in intialise and what is it in render? – Ian Routledge Jul 24 '13 at 16:54
  • see the edited original post. render remains the same throughout – tsjnsn Jul 24 '13 at 16:57
  • @tsjnsn the `this` are different, but what are they? Please provide the `console.log` output for both. – mor Jul 24 '13 at 17:13
  • added the output above. – tsjnsn Jul 24 '13 at 17:20
  • Huh, it does seem like you are using bind correctly, yet somehow the `this` in your second console.log appears to be the return value of a `$.ajax` call. This leads me to wonder something: why does your initialize method have an `e` argument? Normally it would take an options argument ... are you using the view class directly as an event handler or something? Could you please include the code for where you instantiate Navigation.Views.List? – machineghost Jul 24 '13 at 22:54
  • Actually, could you include the code where the `render` method is being invoked too? – machineghost Jul 24 '13 at 22:58
  • 1
    @tsjnsn It looks like your second this is a jQuery promise object: http://api.jquery.com/promise/ . Your trigger is likely fired from a jQuery call and hijacks the `this` keyword. This happens somewhere else in your code. Here is a Fiddle that shows that your code is fine: http://jsfiddle.net/iamor/zNDDM/ – mor Jul 24 '13 at 22:58
  • @machineghost added the code that calls render(). – tsjnsn Jul 25 '13 at 14:58
  • @mor Good to know. Can you write an example that shows how hijacking 'this' occurs? I am not too familiar with that. – tsjnsn Jul 25 '13 at 14:59
  • What's a `Backbone.Layout`? This is clearly coming from some sort of 3rd party library, and that library is very likely the true source of your problem. – machineghost Jul 25 '13 at 15:57
  • https://github.com/tbranyen/backbone.layoutmanager – tsjnsn Jul 25 '13 at 17:26
  • @tsjnsn Looks like Backbone.Layout calls `render` in a particular way: it is asynchronous, hence the `$.promise` for `this` value. Have a look at this documentation: https://github.com/tbranyen/backbone.layoutmanager/wiki/Configuration#rendertemplate-context . Also, according to this line of code: https://github.com/tbranyen/backbone.layoutmanager/blob/master/backbone.layoutmanager.js#L378 it is possible to access the view's `this` by using something like `var self = this.view`. – mor Jul 25 '13 at 18:52
  • @mor this.view is undefined. Also, the point where it calls the render method that I defined is here: https://github.com/tbranyen/backbone.layoutmanager/blob/master/backbone.layoutmanager.js#L472 – tsjnsn Jul 26 '13 at 15:17

2 Answers2

1

Try the following instead:

this.listenTo(this.collection, "add", this.render);

ListenTo was added in 0.9.x and is recommended because "... make it easier to create Views that have all of their observers unbound when you want to remove the view.", quoted from the backbone docs. Here's a good answer that goes into detail about context too which might help: https://stackoverflow.com/a/16824080/486434

Community
  • 1
  • 1
Ian Routledge
  • 4,012
  • 1
  • 23
  • 28
1

Your view render is getting called by this line of code:

https://github.com/tbranyen/backbone.layoutmanager/blob/master/backbone.layoutmanager.js#L472:

  // Render the View into the el property.
  if (contents) {
    rendered = options.render.call(renderAsync, contents, context);
  }

The problem is that options.render.call(renderAsync, contents, context); binds the this keyword with renderAsync which is a promise.

I am not familiar with Backbone Layout Manager, but it looks like the original context is passed along as the second argument to the render method. You can then use it with slight modifications to your render method.

render: function( contents, context ) {
      var self = context ? context : this;    
      var renderedContent = this.template(self.collection.toJSON());    
      return this;
    },
mor
  • 2,313
  • 18
  • 28
  • `context` turns out to be something completely different. If I change L472 to `rendered = options.render.call(renderAsync, contents, root);`, then it gives me the context that you would expect. – tsjnsn Jul 26 '13 at 17:51
  • @tsjnsn I must say that I don't really know why and how Backbone Layoutmanager works but it looks like it does some *black magic* and tries to automate some stuff for you. Clearly, `render` was not meant to be used the way you do. Anyhow, you might want to log the values of the `contents` and `context` variables to look if you can access the `root` object. If you are going to make the `this` context right by modifying L472, you could just change it to: `options.render.call(root, contents, context);` The first argument of `function.call` is going to be the `this` keyword. – mor Jul 26 '13 at 18:17
  • I will try that. My original questions is answered, now I have many more :) Thanks for the help. – tsjnsn Jul 26 '13 at 19:07