0

I have a sequence of page states that essentially mimic a shopping cart checkout process like so:

var ItemsCollection = Backbone.Collection.extend({
    model: ItemModel,    
    url "/items" 
});

var ItemsView = Backbone.View.extend({
    // Select item on click event here
});

var ItemsApp = Backbone.View.extend({
    // Fetch collection of items and render each ItemsView

});

When an Item is selected, I wanted to essentially change state to render Sellers of that item. The architecture would look like this:

var SellersCollection = Backbone.Collection.extend({
    model: SellersModel,    
    url "/sellers" // The item ID is stored in a session NOT in the url (So permalinks work) 
});

var SellersView = Backbone.View.extend({
    // Select item on click event here
});

var SellersApp = Backbone.View.extend({
    // Fetch collection of sellers and render each SellersView

});

So given these two states, where is the based place to instantiate a Sellers Collections, Fetch the Sellers and render the view?

I was thinking of essentially combining the SellersApp view and ItemsApp view into one serving as sort of a controller that determines which subview to render and which collection to fetch. If I do it that way, should I instantiate BOTH collections in the main app namespace and fetch the collections when needed or should I instantiate each collection only when the corresponding state (url) is called. I think the latter approach violates the Law of Demeter.

How I think I should do it.

// 1. Instantiate outside the view
var MainApp  = Backbone.View.extend({

     attributes: {
         "page": "items"
     },

     items: function(){
        // Fetch items collection and render view (listenTo used in initialize)
     },

     sellers: function() {
          // Fetch sellers
     }

});

Items = new ItemsCollection;
Sellers = new SellersCollection;

Is this a good approach? If it is a good approach, where should I tell the MainApp to change states - i.e. should I explicitly invoke the main app's fetch collection method (i.e.
in the ItemsView 'click' event, explicitly declare ItemsApp.sellers) or should I use a listener on the main app view that automatically listens for an item to be selected.

I'm essentially looking for the alternative to use router.navigate - trigger and using the router to instantiate each view/collection as I've heard this is not good practice.

flynn
  • 1,572
  • 2
  • 12
  • 26
  • I'm trying to find the right solution to avoid using the Router's built in trigger:true method. Haven't been able to find a good example. – flynn Aug 16 '13 at 15:58
  • Do you want to show the sellers inline with the list as an expanded view, or change the entire page when you select an item? – Trevor Elliott Aug 16 '13 at 16:44
  • I'd rather had it act as a completely separate state/view. So it'd be the same as if you clicked a permalink to the sellers page where the sellers are based on a stored session item on the server side. – flynn Aug 16 '13 at 16:46

1 Answers1

1

Unfortunately with Backbone (especially Backbone Views) there's not really a "proper" way to do things. There's no reference convention. A lot of people who use Backbone only use the Models/Collections and don't use the Views at all.

In my opinion I would scrap having a view for a collection unless it's actually doing something important. Use the hierarchy App > ModelCollection+ModelViewCollection.

So in your case:

ItemsApp (Backbone.View)
--ItemCollection (Backbone.Collection)
  --Item (Backbone.Model)
    -- SellerCollection (Backbone.Collection)
--ItemViewCollection (Array)
  --ItemView (Backbone.View)
    -- SellerViewCollection (Array)

So your ItemsApp would create and destroy ItemViews as the ItemCollection changes (listen to the events).

The ItemView is responsible to know when the user selects it based on an event. It can then choose to populate the SellerCollection on its model when it is selected. When unselected it could clear that collection. It also listens to changes in the SellerCollection and adds and removes views for each seller.

I don't think there's any built-in method to store a list of Backbone.Views, you probably just want to create your own array. It's up to you to keep track of the views, since a model itself shouldn't have a reference to its view.

It's worth having a global event object to act as a sort of messaging system. Backbone.View implements Backbone.Events so you can declare your app object globally and then listen to any events. You should only use that when you need to though, otherwise you should just listen to events directly instead of triggering them globally. For example, your ItemView may have a "Back" button which raises an event on itself called "back", while your AppView is listening to the events on the the active ItemView and when it wants to go back the AppView will make the necessary changes to the DOM and de-select that item.

Trevor Elliott
  • 11,292
  • 11
  • 63
  • 102
  • How would this interact/work with a permalink? Where a user goes directly to the sellers page with the item stored in a session on the server? Would I use the router to call that collection? Then it may behave differently since in the use case you described the collection is called from within a subview, right? – flynn Aug 16 '13 at 17:26
  • The router raises events such as "route:detail" or whatever the name of your route is, and it passes arguments to those events. So your main ItemsApp listens to the router. eg. `router.on("route:detail", this.RouteDetail, this);` – Trevor Elliott Aug 16 '13 at 19:12
  • And then that function would take the ID or whatever for the Item model, and it would set it as the selected item which should trigger the view changing. Basically you're triggering it from a router event INSTEAD of one of your items raising a click event. As long as you handle the triggering the same, it shouldn't matter how you trigger it. – Trevor Elliott Aug 16 '13 at 19:14
  • This way you could even do something like... in a detail item view you could link to another item. That item view just needs to call `app.trigger("route:edit", id)` to trigger the app to navigate to another item. The app can be responsible for knowing which state its in (List or Detail view) and which item is selected (or null), and it handles DOM transitions and whatnot. – Trevor Elliott Aug 16 '13 at 19:17