开发者

Backbone.js | How can I store elements of a view for retrieval later?

This is my first attempt at using Backbone.js, so I decided to make a simple test application that simulates a restaurant menu with menu items. I was following along with this cool blog post from andyet.net.

I'm having a problem when adding a model to a collection. I have bound a view method to the collection's add event that should update the view. Here is the code with as much irrelevant code removed as possible.

(note: I removed things like local scope var declarations and closures for the sake of brevity. The following is still a little long, and I know that's annoying, but it should be pretty straightforward and easy to understand):

MenuItem = Backbone.Model.extend({
    // initialize functions removed for brevity
});
Menu = Backbone.Model.extend({
    // initialize functions removed for brevity
    // MenuSelection property is defined here.
});
MenuSelection = Backbone.Collection.extend({ model: MenuItem });
MenuItemView = Backbone.View.extend({
    // render template
});

/**
 * This is unaltered from the code.  The error occurs here.
 */
RestaurantAppView = Backbone.View.extend({
    addMenuItem : function (MenuItem) {
        var view = new MenuItemView({ model : MenuItem });
        this.menuList.append(view.render().el);
        // ERROR occurs here.  error: "this.menuList is undefined"
    },
    initialize : function () {
        this.model.MenuSelection.bind('add', this.addMenuItem);
    },
    render : function () {
        $(this.el).html(ich.app(this.model.toJSON()));
        this.menuList = this.$('#menuList'); // IT IS DEFINED HERE
        return this;
    }
});

/**
 * Everything after this I left in just-in-case you need to see it.
 */
RestaurantAppController = {
    init : function (spec) {
        this.config = { connect : true };
        _.extend(this.config, spec);
        this.model = new Menu({
           name : this.config.name,
        });
        this.view = new RestaurantAppView({ model : this.model });
        return this;
    }
};
$(function() {
    // simulating ajax JSON response.
    var json = {
        Menu : {
            name : 'Appetizers',
            MenuItem : [
                {
                    name : 'toast',
                    description : 'very taosty',
                    price : '$20.00'
                },
                {
                    name : 'jam',
                    description : 'very jammy',
                    price : '$10.00'
                },
                {
                    name : 'butter',
                    description : 'very buttery',
                    price : '$26.00'
                }
            ]
        }
    };
    window.app = RestaurantAppController.init({
        name : json.Menu.name
    });
    $('body').append(app.view.render().el);
    app.model.MenuSelection.add(json.Menu.MenuItem);
});  

I've marked with comments the problematic area. According the the Backbone Documentation:

If jQuery or Zepto is included on the page, each view has a $ function that runs queries scoped within the view's element.

So, if I'm setting this.menuList = this.$('#menuList'); in the render method, why can I not access this.menuList in the addMenuItem method? The demo I linked to at the top does it exacly like this. Also, if I swap out this.menuList for a jQuery selector, like so:

addMenuItem : function (MenuItem) {
    var view = new MenuItemView({ model : MenuItem });
    $('#menuList').append(view.render().el);
}

Everything works fine. But I don't want to re-select the menulist ID every time addMenuItem is executed. The RightWayTM is to cache it after it is rendered.

Also note: I thought maybe the probl开发者_开发问答em was with the ICanHaz template not returning fast enough, but then this.menuList would be an empty array, not undefined, so that's not it.


You're running into the #1 gotcha with JavaScript -- the dynamically scoped this keyword. When you pull off this.addMenuItem as a reference without binding it, the addMenuItem function loses its notion of this. There are two easy ways to fix it in your code, either replace this line:

this.model.MenuSelection.bind('add', this.addMenuItem);

With this:

this.model.MenuSelection.bind('add', _.bind(this.addMenuItem, this));

Or add this line to the top of your initialize function, which will accomplish effectively the same thing:

_.bindAll(this, 'addMenuItem');


Probably addMenuItem method is being called before render method is called and hence the definition for menuList is missing in the addMenuItem.

Why don't you push this.menuList = this.$('#menuList'); to initialize method and try?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜