Andreas Köberle
Andreas Köberle

Reputation: 110892

Organize model and collection in backbone

I have a JSON response with the following structure:

{
    id: 1,
    status: 0,
    created_at: "Y:m:d H:m:s",
    updated_at "Y:m:d H:m:s)",
    order_items: [items]
}

Could I make a collection with it? The ID, status etc. are just metadata. Or should I create new ItemsCollection for the items array? Also will I get notified when an item changed?

Upvotes: 5

Views: 542

Answers (4)

umar
umar

Reputation: 910

Yes, you're on the right track. You simply have to define three things:

  1. A Model for your order object
  2. A Model for the order_item object
  3. A Collection for order_items

Remember that every Model is responsible for its own validation, so you need to write a validate function for it.

Then, every time you parse your JSON, in your item's parse method, you need to convert the order_items to a backbone Collection using something like:

parse: function(response) {
    if (!_.isNull(response) && !_.isNull(response.orderitems)) {
        response.orderitems = new OrderItems(response.orderitems);
    }
    return response;        
}

Once you've done this, everything should work as-intended!

Upvotes: 6

Luke
Luke

Reputation: 8407

As described in Backbone (http://backbonejs.org/#FAQ-nested) you should use the following way...

Edit: You do not need any plugins. Futhermore I do not recommend this, as with Backbone you can be sure you have a rock-solid framework, but they do not check how rock-solid is any plugin they propagate on their website. I had many projects where I had to think about nesting. In the end, the here shown way was the most secure and the most conventional.

Strucutre

var Order = Backbone.Model.extend({
   initialize : function(data) {
      this.orderItems = new OrderItems(null, {order:this});
      if (!!data.order_items) {
          this.orderItems.reset(data.order_items, {parse:true});
          //we dont want nesting in "attributes"
          this.unset("order_items");
      }
   },
   // this is used, when a change from server happens. So when
   // the model already exists, and the stuff in initialize
   // wouldnt be called anymore.
   parse : function(data) {
       if (!!data.order_items) {
           this.orderItems.reset(data.order_items);
           delete data.order_items;
       }
       return data;
   }
});

var Orders = Backbone.Collection.extend({
   model : Order
});

var OrderItem = Backbone.Model.extend({

});

var OrderItems = Backbone.Collection.extend({
   model : OrderItem,
   initialize : function(data, options) {
      if (options && options.order) {
          this.order = options.order;
      } 
   }
};

Binding

myOrder.orderItems.on("...");

myOrder.on("...");

Access

access your Order from within the OrderItems.

var someOrderItem = myOrders.get(1).orderItems.first();
someOrderItem.collection.order === myOrders.get(1)
// will result in "true"

Lazy loading

sometimes we dont want to load everything in one request. Most of the time I prefer to load the base collection, and only when a view does load one model, and the model has subcollections, I retrieve them later. For this I have a fetchOnce method.

var OrderItems = Backbone.Model.extend({
    initialize : function() {
        if (options && options.order) {
           this.order = options.order;
        }
        this.fetchOnce = _.once(this.fetch, this);
        //this will always return you an jquery xhr deferred.
    },
    url : function() {
        if (this.order) {
           // url() would return you something like "/order/1234"
           return this.order.url() + "/items"
        }
    }
);

Then somewhere else, you would initialize and fetch your complete orders.

   var orders = new Orders();
   orders.fetch();

if you then do some edit view and want all order items to be present too, you would do following.

   var view = new Backbone.View({
      order : orders.get(1)
   });

   view.order.orderItems.fetchOnce().done(function() {
       view.render();
   });

as fetchOnce returns a new deferred if not existant, or an existing (because of _.once) you can always bind to it, if it was called already previously, you will be notified in the done too...

Upvotes: 3

Justin Alexander
Justin Alexander

Reputation: 2024

You could also define it dynamically:

var Order = Backbone.Model.extend({
   initialize: function () {
      this.getItems = _.memoize(this.getItems); // not necessary, but improves performance
   },
   getItems: function () {
     var that = this; 
     var col = new Backbone.Collection(this.get('order_items'));
     col.on('change', function() {
                          that.set({order_items: this.toJSON()});
                      });
     return col;
   }
})

Any change to the returned collection would automatically update the parent model. Note: the parent model can only listen for changes to 'order_items' not it's children.

Upvotes: 0

Derick Bailey
Derick Bailey

Reputation: 72858

you can do all of that, but you have to do it yourself or with a plugin. chances are, the code you end up writing will be similar to what's available in the Backbone.Relational plugin:

http://github.com/PaulUithol/Backbone-relational

i recommend using that plugin instead of rolling the code yourself

Upvotes: 3

Related Questions