2
votes

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;
});
2

2 Answers

1
votes

In backbone models there are actually two kinds of id's, the first is id which is meant to represent the id of your model on the server and isn't atomically assigned. The second is cid (client id) which backbone will atomically generate and assign for you.

In case your server model's id property isn't named id you can map it by setting the idAttributemodel property. For example if the id in your json model is called jsonID

var GroceryItems = Backbone.Collection.extend({

    model: GroceryItem,
    idAttributemodel: jsonID,

    url: "data.json",

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

    }

});

Some more info on id, cid, idAttribute

I see that in your GroceryItems collection file you are both declaring your collection and instantiating it, it might make more sense in this case to just declare it and return and in your App View (or wherever you declare your collection view) instantiate it there and pass it to the view.

In order to retrieve the model id on the click event of the li, you have to options either you have a seperate view per each li which is bound to a specific model, or in your case where you are rendering all your models using the same view you can retreive it from the DOM.

For example

 getModel: function (e) {
        //you might want to consider using data attributes instead
        var modelId = $(e.currentTarget).attr('id'); 
        var model = this.storeItems.get(modelId);
    },

In general regarding using require.js I think that while there is a bit of a learning curve in the long run it is worth it. One thing you might want to consider doing is keeping one file per view/model/collection.

0
votes

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()));
    }
});