Workflow In Backbone Apps: Triggering View Events From DOM Events

In my previous blog post, I talked about modeling an explicit workflow in JavaScript and Backbone application. The last code sample I showed had the implementation of the workflow object itself, but omitted all of the details of the views that I was using.

Here’s the gist of that again:

orgChart = {

  addNewEmployee: function(){
    var that = this;

    var employeeDetail = this.getEmployeeDetail();
    employeeDetail.on("complete", function(employee){

      var managerSelector = that.selectManager(employee);
      managerSelector.on("save", function(employee){
        employee.save();
      });

    });
  },

  getEmployeeDetail: function(){
    var form = new EmployeeDetailForm();
    form.render();
    $("#wizard").html(form.el);
    return form;
  },

  selectManager: function(employee){
    var form = new SelectManagerForm({
      model: employee
    });
    form.render();
    $("#wizard").html(form.el);
    return form;
  }
}

// implementation details for EmployeeDetailForm go here

// implementation details for SelectManagerForm go here

// implementation details for Employee model go here

I’ve implemented various views and objects in various manners, in order to facilitate that workflow, but what it always comes down to is that the objects facilitating the workflow need to trigger events. 

A Basic Implementation

The most common way of triggering an event from a Backbone.View is to have some DOM events handled, and from the event handler/callback method for that, trigger the event you want:

SomeView = Backbone.View.extend({

  events: {
    "click .next": "nextClicked",
    "click .previous": "previousClicked"
  },

  nextClicked: function(e){
    e.preventDefault();
    this.trigger("next")
  },

  previousClicked: function(e){
    e.preventDefault();
    this.trigger("previous");
  }

});

This works well. I’ve done this more times than I can count. But there’s a lot of redundancy here. You can see that both of the DOM events that I’m handling have a callback method, and both of the callback methods do nothing more than trigger an event from the view itself so that the workflow can move forward.

We can do better than that… enter Marionette’s new “triggers”.

Reducing Event Triggering To Configuration

With the v0.8.2 release of Marionette, I’ve introduced the idea of view triggers – a way to configure a DOM event to trigger a view event. So instead of having to write all of that redundant code, where the only real difference is the name of the callback method and the name of the event that’s triggered, we can write this:

SomeView = Backbone.Marionette.ItemView.extend({

  // ...

  triggers: {
    "click .next": "next",
    "click .previous": "previous"
  }

});

Must shorter. Much cleaner.

You can see that the left side of the hash is a standard Backbone.View events setup where you specify the DOM event and element selector. Then on the right hand side, specify the name of the event that you want the view to trigger when that DOM event fires. Then, when I click on the “.next” element, the view will trigger a “next” event for me. Similarly, clicking the “.previous” element will trigger a “previous” event.

Trigger From Any Marionette View

Triggers can be used from any Marionette view. You’re not limited to the ItemView type that I showed. In fact, the trigger functionality is built in to the base Backbone.Marionette.View, so even if you are creating your own specialized view type, you only need to extend from that in order to get this functionality.

Limitations

Of course this isn’t a “complete” solution for handling events in your views, by any means. If you need to do anything more than trigger the event by itself, you should still use a standard event handler and callback method. At this point, there’s no way for you to configure any kind of data or objects to be passed through the event itself. But this may be added at some point in the future. I wrote this fairy quickly last night, and it suits my needs right now. 

For simple scenarios where you just need to trigger an event from the view, though, this works well. I’m using it in several places where my workflow is just to display a button or a link, and have that clickable item move on to another part of the application.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Javascript, Workflow. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://twitter.com/dagda1 dagda1

    I’ve used Backbone and Ember in roughly equal amounts and the Ember bindings model is a much leaner, looser coupled and all round better way of dealing with things.

    I can have several parts of my app updating from one change using bindings.  For example:

    http://bit.ly/KYFmW5

    In the above example I have  a queryBinding where I specify the path to the bound object.  Once that object changes, I perform a transform and the change gets published to any subscribers like the queryDidChange handler in the above gist. I can have several other observers throughout my app and observers on these observers etc. and this is all synchronised beautifully without me having to write any additional code.

    BTW, I use Backbone.Marionette on any legacy backbone apps and it has been a real time saver. It really does take the pain out of backbone.

  • Bernardo

    Derick,

    I just started working with Marionette and am working specifically with the triggers pattern. I noticed, however, that you’re making the view trigger the event, instead of the App.vent object – any particular reason for that?

    The reason I’m asking is that once I start using the App.vent object I don’t plan on having individual views trigger anything, in fact, I don’t even want to keep track of individual views. I ‘new’ a view directly into the region’s show method (since you so graciously clean up after me anyway within the show method):

    MyApp.region.show(new MyView(…));

    and would like to do this:

    MyApp.vent.on(“myview:clickevent”, …);

    I have already written the small BB.Marionette hack to make this work, but would like your input as to why or why not to do this.

    Thanks!

    • http://mutedsolutions.com Derick Bailey

      this is a very bad idea for a number of reasons. most of them come down to context and coupling.

      let’s say you have 3 separate worflows… one for adding an employee, one for adding a contractor, and one for adding a vendor. in all three of these situations you might have a cancel button on one of the views in the workflow. if you trigger “cancel” from each of these three views, all three of the workflows will receive the cancel event because you are using a global event aggregator. this will cause really big, horribly problems with your app, long term.

      to fix the “cancel” event problem, you have to hack your events the way you have done, and prefix them with the view’s type name. this is very tightly coupling your events to your views. the purpose of the events is to decouple the parts of the app, not couple them more tightly with magic strings. if you change your view’s name, you have to hope you can find all of the places in your code that use this view’s name in an event, so you can change them all and keep your code up to date.
      unit testing: if you are trying to test your view implementation, now you have added another requirement for your test set up: the application object must be instantiated, and must provide it’s vent for use. this is going to turn in to a nightmare when you have to set up the same global objects over and over and over in your unit tests.

      you no longer have a modularized system. you are now relying on global objects, which means you have tightly coupled your code to parts of the app that they should not have to know about. this means you can’t take your app’s module and re-use it somewhere else in the same system even, without bringing along everything it’s coupled too.

      you seem to be wanting to push all of the responsibility of every part of the system up to the application object. this flies in the face of every object oriented software development principle i can think of. giant “god objects” that handle everything (also known as “big ball of mud”) are one of the worst anti-patterns in software design.

      and on top of all that, you’ll have to maintain your hack in your system every time a new version of Marionette is released. that will get tedious, quickly. but that’s a minor problem compared to the other issues.

      • Nathaniel Tucker

        And what about global triggers, like say a link navigation? I have to repeat myself for every single view instantiation (which is spread everywhere in my code – so hard to keep track!) for one simple function.

        App.registerLinkNav = function(args) {
        var $target = $(args.event.target);
        if ($target.prop(“tagName”)!=”A”) {
        $target = $target.parent(“a”).first();
        }
        App.router.navigate($target.attr(‘href’), {trigger: true});
        };

        (And no, every link doesn’t trigger this, just certain ones – but that’s matched by the trigger selector)

  • http://www.facebook.com/scoarescoare Scott Coates

    Where do you typically attach events to a view? From within an Application object? I’m guessing since Marionette strives for decoupled views, you don’t often have one view tie into another view’s events. Do you typically have CollectionViews tie into its ItemView’s events?

  • http://www.facebook.com/scoarescoare Scott Coates

    I see the configureTriggers function calls event.stopPropagation and preventDefault. I’d actually like to have these events continue on so the url in the address can change (
    http://stackoverflow.com/questions/9328513/backbone-js-and-pushstate/9331734#9331734)

    Thoughts?

    • trojjer

      Yeah, use the events hash and handle it yourself in such cases ;)

  • tom

    Hi I’m trying to implement a wizard..
    I wanted to have a workflow controller which have the steps in some kind of order..

    I was just wondering when using triggers would you set it up in a pub sub kind of way? i.e. if i have a trigger of next how would i get some kind of workflow controller to subscribe and display the next view in a content region with the model?

    how would that look in backbone with marionette.. im very new to it all

  • Philippe Costa

    What’s up? Just a question. What does the view pass as argument to the event handler?