7

I'm just getting started with Backbone. I went through the first two PeepCode screencasts which were great and now I'm digging in on a quick detached (no server side) mock-up of a future app.

Here's what I'm looking to build (roughly). A series of five text boxes - lets call these Widgets. Each Widget input, when selected, will display a pane that shows Tasks associated with the Widget and allow the user to create a new Task or destroy existing Tasks.

At this point, I'm thinking I have the following models:

Widget
Task

The following collections:

Tasks
Widgets

The following views (this is where it gets hairy!)

WidgetListView
  - Presents a collection of Widgets
WidgetView 
  - sub-view of WidgetListView to render a specific Widget
TaskPaneView 
  - Presented when the user selects a Widget input
TaskCreateView 
  - Ability to create a new Task associated with selected Widget
TaskListView 
  - Presents a collection of Tasks for the given widget
TaskView 
  - Displays Task detail - sub-view of TaskListView

Assuming that's reasonable, the trick becomes how to display a TaskPaneView when a WidgetView is selected. And futhermore, how that TaskPaneView should in turn render TaskCreateViews and TaskListViews.

The real question here is: Does one cascade render events across Views? Is it permissible for a Root view to know of sub-views and render them explicitly? Should this be event-driven?

Apologies if this is an open-ended question, just hoping someone will have seen something similar before and be able to point me in the right direction.

Thanks!

Cory
  • 2,538
  • 2
  • 18
  • 19
  • 1
    you might want to check out Marionette for the same reason @Luc Perkins suggested Backbone Aura. Marionette may be a little more mature than Aura – b_dubb Sep 24 '13 at 19:27
  • I think TaskCreateView and TaskPaneView is unnecessary. TaskCreateView in particular should not be there as it doesnt have any model to represent. Incorporate Task creation in TaskListView itself. Maybe an input control which will allow users to add new task. And when user press Enter key add it to the Tasks collection. I am assuming that you are listening for add event on collection, so that you can add the newly created TaskView to the TaskListView. You can bring up TaskListView whenever user focus on WidgetView. – Vishal Dec 17 '13 at 11:45

5 Answers5

15

Definitely make it event driven. Also, try not to create views that are closely coupled. Loose coupling will make your code more maintainable as well as flexible.

Check out this post on the event aggregator model and backbone:

http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/

The short version is you can do this:

var vent = _.extend({}, Backbone.Events);

and use vent.trigger and vent.bind to control your app.

Chris Biscardi
  • 3,148
  • 2
  • 20
  • 19
  • Very interesting. I'll be back with an upvote. Any idea on whether or not it's cool for a root view to have subviews? For instance - does TaskPaneView - which would house Create and List even make sense? – Cory Dec 05 '11 at 15:11
  • 1
    no problem at all, nested views is very much possible, and i can only speak for myself saying i use them often, the easiest example would be having a catalog of books, with a ListView and each item as a view on it's own, but it can go mutch further than that. – Sander Dec 05 '11 at 22:54
  • 1
    It's cool as in it works. It's not cool as in it will be harder to maintain your code. Modularize and use events to trigger renders. If you use the event agg pattern, you can do something like vent.trigger('create', model); which will pass the model to the bound callbacks (in this case: a function that renders a view). In this situation your create view is now a reusable component of your app that you can call from anywhere. – Chris Biscardi Dec 06 '11 at 00:48
  • Thanks all - going to do a bit more digging here and see what I can learn. I dig the vent stuff. Much to learn. – Cory Dec 06 '11 at 02:47
12

Pre p.s.: I have made a gist for you with the code that I wrote below: https://gist.github.com/2863979

I agree with the pub/sub ('Observer pattern') that is suggested by the other answers. I would however also use the power of Require.js along with Backbone.

Addy Osmani has written a few GREAT! resources about Javascript design patterns and about building Backbone applications:

http://addyosmani.com/resources/essentialjsdesignpatterns/book/ http://addyosmani.com/writing-modular-js/ http://addyosmani.github.com/backbone-fundamentals/

The cool thing about using AMD (implemented in Require.js) along with Backbone is that you solve a few problems that you'd normally have.

Normally you will define classes and store these in some sort of namespaced way, e.g.:

MyApp.controllers.Tasks = Backbone.Controller.extend({})

This is fine, as long as you define most things in one file, when you start adding more and more different files to the mix it gets less robust and you have to start paying attention to how you load in different files, controllers\Tasks.js after models\Task.js etc. You could of course compile all the files in proper order, etc, but it is far from perfect.

On top of this, the problem with the non AMD way is that you have to nest Views inside of each other more tightly. Lets say:

MyApp.classes.views.TaskList = Backbone.View.extend({ 
    // do stuff
});

MyApp.views.App = Backbone.View.extend({
    el: '#app',
    initialize: function(){
        _.bindAll(this, 'render');
        this.task_list = new MyApp.classes.views.TaskList();    
    },

    render: function(){
        this.task_list.render();
    }
});

window.app = new MyApp.views.App();

All good and well, but this can become a nightmare.

With AMD you can define a module and give it a few dependencies, if you are interested in how this works read the above links, but the above example would look like this:

// file: views/TaskList.js
define([], function(){

    var TaskList = Backbone.View.extend({
        //do stuff
    });

    return new TaskList();
});

// file: views/App.js
define(['views/TaskList'], function(TaskListView){

    var App = Backbone.View.extend({
        el: '#app',

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

        render: function(){
            TaskListView.render();
        }
    });

    return new App();
});

// called in index.html
Require(['views/App'], function(AppView){
    window.app = AppView;
});

Notice that in the case of views you'd return instances, I do this for collections too, but for models I'd return classes:

// file: models/Task.js
define([], function(){

    var Task = Backbone.Model.extend({
        //do stuff
    });

    return Task;
});

This may seem a bit much at first, and people may think 'wow this is overkill'. But the true power becomes clear when you have to use the same objects in many different modules, for example collections:

// file: models/Task.js
define([], function(){

    var Task = Backbone.Model.extend({
        //do stuff
    });

    return Task;
});

// file: collections/Tasks.js
define(['models/Task'], function(TaskModel){

    var Tasks = Backbone.Collection.extend({
        model: TaskModel
    });

    return new Tasks();
});

// file: views/TaskList.js
define(['collections/Tasks'], function(Tasks){

    var TaskList = Backbone.View.extend({
        render: function(){
            _.each(Tasks.models, function(task, index){
                // do something with each task
            });
        }
    });

    return new TaskList();
});


// file: views/statistics.js
define(['collections/Tasks'], function(Tasks){

    var TaskStats = Backbone.View.extend({
        el: document.createElement('div'),

        // Note that you'd have this function in your collection normally (demo)
        getStats: function(){
            totals = {
                all: Tasks.models.length
                done: _.filter(Tasks, function(task){ return task.get('done'); });
            };

            return totals;
        },

        render: function(){
            var stats = this.getStats();

            // do something in a view with the stats.
        }
    });

    return new TaskStats();
});

Note that the 'Tasks' object is exactly the same in both views, so the same models, state, etc. This is a lot nicer than having to create instances of the Tasks collection at one point and then reference it through the whole application all the time.

At least for me using Require.js with Backbone has taken away a gigantic piece of the puzzling with where to instantiate what. Using modules for this is very very helpful. I hope this is applicable to your question as well.

p.s. please also note that you'd include Backbone, Underscore and jQuery as modules to your app too, although you don't have to, you can just load them in using the normal script tags.

Mosselman
  • 1,718
  • 15
  • 27
1

For something with this kind of complexity, I might recommend using Backbone Aura, which has not yet had a stable release version. Aura essentially allows you to have multiple fully self-contained Backbone apps, called "widgets," running on a single page, which might help disentangle and smooth over some of your model/view logic.

0

From a classical MVC perspective, your views respond to changes in their associated models.

//initialize for view
initialize : function() {
    this.model.on("change", this.render(), this);
}

The idea here is that anytime a view's model is changed, it'll render itself.

Alternatively or additionally, if you change something on a view, you can trigger an event that the controller listens to. If the controller also created the other models, it can modify them in some meaningful way, then if you're listening for changes to the models the views will change as well.

Brendan Delumpa
  • 1,155
  • 1
  • 6
  • 11
  • 1
    Interesting to see that you can extend Backbone.Events. I myself use a 'Pure JS pub/sub' implementation that I got from here: http://jsperf.com/pubsubjs-vs-jquery-custom-events/26 I really suggest checking it out for anyone at all into pub/sub or javascript performance. – Mosselman Jun 03 '12 at 15:19
  • 1
    Actually, you can't extend Backbone.Events. It's a simply a hash that you can add to the prototype of custom object to enable it to send and receive events. – Brendan Delumpa Jun 04 '12 at 20:50
0

Similar answer to Chris Biscardi's. Here's what I have:

You create a global var Dispatcher (doesn't have to be global as long as it can be accessed from the scope of Backbone app):

Dispatcher = _.extend({}, Backbone.Events);

Dispatcher will help you execute subscribed callbacks with events that are not particularly triggered by changes in models or collections. And cool thing is that Dispatcher can execute any function, inside or outside Backbone app.

You subscribe to events using bind() in a view or any part of the app:

Dispatcher.bind('editor_keypress', this.validate_summary);

Then in another view or part of the app you trigger new event using trigger():

Dispatcher.trigger('redactor_keypress');

The beauty of using a dispatcher is its simplicity and ability to subscribe multiple listeners (e.g. callbacks in different Backbone views) to the same event.

mvbl fst
  • 5,213
  • 8
  • 42
  • 59