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

This is my first attempt at using Backbone.js, so I decided to make a simple test application that simu

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.

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 ments 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 problem 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.

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.

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 ments 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 problem 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.

Share Improve this question asked Dec 27, 2010 at 22:55 StephenStephen 19k10 gold badges62 silver badges98 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 8

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 acplish 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?

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745089803a4610625.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信