Search code examples
backbone.js

Backbone Model Ids and on click events


Alright. I'm going to give in here and ask for some help. I think I'm running into multiple issues and not sure of the best approach. I'm using handlebars to create an ul of li that are my json objects to backbone models. Now that the template works I want to on click get the model to be able to add to another collection.

My first problem. I thought backbone defined a model id by default? If i set default to "" or null every model's id is "" or null. The json objects have an id I could assign to the backbone id but if I do that in the defaults id: jsonID, the jsonID is undefined. If I do object.toJSON() in the console there is no backbone created id. So I don't have a value to use in the handlebars template to assign my div id to the backbone id for that model. To then use that to get element id so that on click I could get the backbone id. Or at least I've read a lot of examples that do it that way.

My second issue, I think stems from requirejs. All the examples I see for click even use this.collection or this.model. On my View file those always return undefined, assuming this is because of requirejs. I tried this example in particular http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/. I'm wondering if I should just scrap using requirejs, it seems to cause more problems then help.

Here is my code so far, I deleted out my click function code because none of it was working.

Collection File:

define(['jquery', 'backbone', 'lodash', 'Models/GroceryItem'],

    function($, Backbone, _, GroceryItem) {

    var GroceryItems = Backbone.Collection.extend({

        model: GroceryItem,

        url: "data.json",

        parse: function(response) {
            return response.all_coupons;

        }

    });

    var storeItems = new GroceryItems();
    storeItems.fetch({
    success:function(){
        console.log(storeItems.toJSON());
        }
    });

    return storeItems;

});

View File:

define(['jquery', 'backbone', 'lodash', 'handlebars', 'Collections/GroceryItems'],

    function($, Backbone, _, Handlebars, storeItems) {



    var GroceryItemsView = Backbone.View.extend({

        template: Handlebars.compile(
            '<ul class="d-row">' +
                    '{{#each storeItems}}' +
                        '<li class="lineItem" id="{{coupon_id}}">' +    
                            '<div class="wrapper">' +
                                '<div class="header">{{coupon_title}}</div>' +              
                                    '<div class="column_wrapper">' +
                                        '<div class="two-col">' +
                                            '<div class="product_image"><img src="{{coupon_thumb}}" alt="{{coupon_description}}" height="110" width="110"></div>' +
                                            '<div class="description">{{coupon_description}}</div>' +
                                         '</div>' +
                                    '</div>' +
                                '<div class="expiration">Valid From: {{valid_from}} to {{valid_to}}</div>' +    
                            '</div>' +
                        '</li>' +
                '{{/each}}' +
            '</ul>'
            ),

        events: {
            "click li": "getModel"
        },

        getModel:function(e){

        },

        render: function() {
            var that = this;
            storeItems.fetch({
                success: function(storeItems) {
                    var storeTemplate = that.template({storeItems: storeItems.toJSON()});
                    that.$el.html(storeTemplate);
                    return that;
                }
            })          
            return this; 
        }
    });

    return GroceryItemsView;

});

Thanks a bunch for any help. It's much appreciated. If I'm going at this completely wrong, I'm open to any suggestions. I'm just learning backbone and javascript in general so I'm grinding away as I go with a lot of googling.

Thanks!

EDITED CODE:

define(['jquery', 'backbone', 'lodash', 'Collections/GroceryItems', 'Views/GroceryItemView'],

    function($, Backbone, _, storeItems, GroceryItemView) {

    var GroceryItemsView = Backbone.View.extend({

        tagName: 'ul',
        className: 'd-row',
        el: '#container',
        initialize: function () {
            //basically says only render when collection syncs
            this.listenTo(storeItems, 'sync', this.render);
        },

        render: function () {
            //got keep track of views for when you need close them (not important for now but you'll thank me later)
            this.groceryItemsView = [];
            storeItems.each(function (GroceryItem) {
                //we are making a new view for each model and passing it in as an option
                var itemView = new GroceryItemView({
                    model: GroceryItem
                });
                //The view creates an element but it is not attached to DOM. We will attach it to the ItemsView's $el (which IS attached to the DOM)
                this.$el.append(itemView.$el);

                this.groceryItemsView.push(itemView);
            }, this);
            }
        });

    var list = new GroceryItemsView();

    return list;

});

define(['jquery', 'backbone', 'lodash', 'handlebars', 'Views/GroceryItemsView', 'Models/GroceryItem'],

    function($, Backbone, _, Handlebars, GroceryItemsView, GroceryItem) {

        var GroceryItemView = Backbone.View.extend({

        template: Handlebars.compile(
            '<div class="wrapper">' +
                '<div class="header">{{coupon_title}}</div>' +
                    '<div class="column_wrapper">' +
                        '<div class="two-col">' +
                            '<div class="product_image"><img src="{{coupon_thumb}}" alt="{{coupon_description}}" height="110" width="110"></div>' +
                            '<div class="description">{{coupon_description}}</div>' +
                        '</div>' +
                    '</div>' +
                '<div class="expiration">Valid From: {{valid_from}} to {{valid_to}}</div>' +
            '</div>'
        ),

        tagName: 'li',

        className: 'lineItem',

        events: {
            'click': 'getModel'
        },

        initialize: function () {
           this.render();
        },

        getModel: function () {
            return this.model;
        },

        render: function () {
            this.$el.html(this.template(this.model.toJSON()));
        }
    });

    return GroceryItemView;
});

Solution

  • The easiest way to get to a model of something you clicked is surprisingly simple.

    1. I STRONGLY recommend NOT relying on IDs. It's very bad practice. The whole point of using Models is to stop worrying about IDs :P
    2. Creating a Backbone View is not as expensive as some people say. It's actually quite efficient as long as you clean up properly. Break up every logical unit of DOM into it's own View. ESPECIALLY collections of Views
    3. Require is AWESOME. Don't give up on it. Once you figure it out you'll never want to go back. Just think of it as saving bunch of code from another file to a variable defined up top
    4. Don't use success option. Only listen to the sync event. Makes code cleaner, prevents loooooots of weird issues later on.

    I haven't tested this code but the logic works (have done it many times)

    //Preferrably keep this in a separate file or use require-handlebars
    var itemTpl = Handlebars.compile(
        '<div class="wrapper">' +
        '<div class="header">{{coupon_title}}</div>' +
        '<div class="column_wrapper">' +
        '<div class="two-col">' +
        '<div class="product_image"><img src="{{coupon_thumb}}" alt="{{coupon_description}}" height="110" width="110"></div>' +
        '<div class="description">{{coupon_description}}</div>' +
        '</div>' +
        '</div>' +
        '<div class="expiration">Valid From: {{valid_from}} to {{valid_to}}</div>' +
        '</div>');
    
    //Your Collection
    var GroceryItems = Backbone.Collection.extend({
    
        model: GroceryItem,
    
        url: "data.json",
    
        parse: function (response) {
            return response.all_coupons;
        }
    
    });
    
    //This Represents all your views
    var ItemsView = Backbone.View.extend({
        tagName: 'ul',
        el: '.where-this-is-supposed-to-go',
        initialize: function () {
            this.collection = new GroceryItems();
            //basically says only render when collection syncs
            this.listenTo(this.collection, 'sync', this.render);
        },
    
        render: function () {
            //got keep track of views for when you need close them (not important for now but you'll thank me later)
    
            this.itemViews = [];
            this.collection.each(function (m) {
                //we are making a new view for each model and passing it in as an option
                var itemView = new ItemView({
                    model: m
                });
    
                //The view creates an element but it is not attached to DOM. We will attach it to the ItemsView's $el (which IS attached to the DOM)
                this.$el.append(itemView.$el);
    
                this.itemViews.push(itemView);
            }, this);
        }
    });
    
    var ItemView = Backbone.View.extend({
        template: itemTpl,
        tagName: 'li',
        className: 'lineItem',
        events: {
            'click': 'getModel'
        },
        initialize: function () {
           this.render();
        },
        getModel: function () {
            //it's already there. No IDs
            return this.model;
        },
        render: function () {
            this.$el.html(this.template(this.model.toJSON()));
        }
    });