Quantcast
Viewing latest article 1
Browse Latest Browse All 3

Structuring Javascript with Marionette Modules

Image may be NSFW.
Clik here to view.
gmail
Building rich interactive user interfaces is hard. They are hard because those interactions introduce a lot of complexity and the nature of a UI means that everything has a tendency to get coupled to everything else. Not only that, if you are building a competitive web application nowadays, chances are it is a “single page” javascript client which communicates with a server over HTTP.

This introduces a whole new set of problems, even with all of its modern day improvements, javascript still only really gives you very low level constructs out of the box. If you only have expertise in older style server side applications, you are also moving from a stateless world where everything gets refreshed on every action, to a stateful world where everything sticks around.

Since these are such common problems, the JavaScript world has seen many libraries emerge that bring tools and techniques which have proven useful solving these problems on other platforms over the last 40 years or so. The first (and still the most popular) of these is Backbone.js.

Backbone and Backbone.Marionette

Backbone has an explicit goal of providing a set of fundamental structural constructs that are applicable when writing any sort of complex javascript. That is great, but there are still a lot of things missing that you need in every javascript application past a certain level of complexity.

Backbone.Marionette fills this gap by taking the ideas of Backbone a few steps further. What Marionette provides can be split into two parts.

Marionette Views

Marionette provides higher level view constructs then Backbones generic View. Marionette provides an ItemView (for when you want to have a view for a model), a CollectionView (for rendering collections), and a CompositeView (for rendering a model and a collection). By providing these more focused views, Marionette is able to eliminate significant amounts of very common boilerplate that exists in every pure Backbone application, by frameworking away a lot of “plumbing” code.

Marionette Modules

While on the surface, Marionette views provide the biggest wins, modules are what will save your application from becoming a big ball of mud. A common pitfall in Object Oriented design is when you model lower level ideas, but do not have anything abstracting tasks, processes, or workflows.

Without these centralized “coordinators”, Object Oriented code tends to get very hard to trace since complex tasks require interactions across many objects in many files. When you have a coordinator as an abstraction in a single place, then these complex interactions become much easier to understand.

Marionette modules provide a great place to put this coordination code, and also provides clear delineation between these “families” of objects in your application.

Unfortunately, understanding how to use them correctly is not really straight forward unless you are aware of the problems that creep up without them. Combined with sparse documentation, this means that modules in Marionette tend to be misunderstood more often then not.

Understanding where Modules fit in by building an Application.

It is hard to explain where modules fit in, since they can be overkill with simple code, and it is hard to wrap your head around complex code which you don’t work with. So I am going to tell a story of an application which will be all too familiar to people who have gone down this road before.

Getting started: Marionette.Application

The place to start with modules is to create an Application object. An Application object can be started and stopped. It can emit events, and mediate communications between its sub modules. You can also add Regions, which provide a layer between your view code and the DOM. At this stage, things are pretty straight forward

// application.js
var Todo = new Backbone.Marionette.Application();

App.addRegions({
  activeList: '#content',
  navigation: '#sidebar'
});

App.addInitializer(function(){
  var list = new App.TodoList();
  var nav = new App.MainSidebar();

  this.activeList.show(list);
  this.navigation.show(nav);

  Backbone.History.Start();
});

<!-- index.html -->
<script type="text/javascript">
  App.start()
</script>

We now have a “page object” global we can hang the rest of our application off of. We also have a nice way to handle initialization and wiring up of our views to the DOM in a way that keeps a strong separation between page structure and our views, which is a much bigger win. But we are just getting started Image may be NSFW.
Clik here to view.
:)

As time goes on….

What we have will actually take us pretty far. We make some views to show todos and persist them to the server. As the MainSidebar and TodoList views grow, they begin to get harder to manage, so we break them into more granular objects and everything is great again.

But as time goes on, we keep implementing stories that require the Sidebar and the TodoList to know more and more about each other. This isn’t a problem until we start changing the internal structure in one and find seemingly unrelated things on the other side breaking and needing to change as well.

Eventually, even small changes result in multiple days of work, we all get depressed and begin to hate our jobs, the application, and life itself. Obviously, we want to avoid that downward spiral of misery. The only way to avoid it is by evolving the design to handle this complexity and isolate the effects of change.

Isolating change with modules

The point where modules make an appearance is when we start breaking apart TodoList and MainSidebar into smaller pieces. Small pieces interacting together help manage the growing complexity when implementing new ideas, but they introduce API complexity, which makes them harder to work with as a cohesive unit from other parts of the page.

That API complexity stems from when the small pieces of one high level idea know about the small pieces of another high level idea. You can recognize this, when a change to one ends up causing changes in another. In the real world, I have seen the structural change of a central component cascade out through the entire system. This is the beginning of that downward spiral of horror and needs to be rigorously avoided if you plan on scaling and maintaining any application over time and remaining happy.

This is where Modules come in. They abstract these families of functionality into something with high level methods of interaction. If we have a component inside a module, it is only allowed to interact with components from the same module. If it needs to communicate with the rest of the page, it does so through its module.

These high level methods provide barriers to the effects of change and allow you to freely change components without having to worry about the entire system breaking.

Using modules

Lets make a module

// in sidebar.js

// module definition args are optional, but help with encapsulation
// I recommend making a macro/template in your editor of choice
App.module("Sidebar", function(Sidebar, App,
                               Backbone, Marionette,
                               $, _ ){

  // lower level components go here.
    // If this gets too unwieldy, a module definition
  // can span multiple files

  Sidebar.addInitializer(function(){
    var sidebar = new SidebarView();
    App.navigation.show(sidebar);
  });
});

Simple right? But where does the communication come in?

Module Communication

There are three ways that modules can talk to each other. There are

  • Commands – a way to provide application functionality
  • Request/Response – a way to ask/provide a piece of information
  • Events – a way to let the application know something has happened

Practically, there is very little difference between them other then semantics. However, as Derick Bailey (the creator of Marionette) has said, semantics are incredibly important until we learn to communicate in some way other then using language.

Commands

The reason to use a command is to say that this is something that could be triggered from anywhere, but will be handled at a single point. A command can be implemented like this

// inside a module

App.commands.register("notify:success", function(message){
  Notifier.success(message);
});

// some other module

App.execute("notify:success", "WOOHOO!!");

Request/Response

The reason for a request/response pattern is when you want to know something that another module knows or is able to find out. Returning a promise here is often quite useful, I highly recommend reading up on $.Deferred() if you haven’t already. It is an implementation of the CommonJS/Promise API, which is an essential tool for any javascript developer.

Request/response looks like this

Tasks = {
  findByCategory: function(category){   
    var response = $.Deferred();

    $.getJSON('/categories/'+ category, function(taskData){
      var tasks = new TaskCollection(taskData);
      response.resolve(taskData);
    });

    return response.promise();
  }
}

App.reqres.register("tasks:by-category", function(category){
   return Tasks.findByCategory(category)
});

// somewhere else

var request = App.request("tasks:by-category", "never-gonna-do-it")
request.then(function(tasks){
  var newList = new TaskList({collection: tasks});
  App.mainList.show( newList );
});

Events

Events are sort of like commands, only instead of being handled in one places, there is the potential for them to be handled in many places. When you use them, you are saying “I just want to let the world know something happened.”

App.vent.bind("application:changed", function(){
  Cache.clear()
});

// somewhere else

App.trigger("application:changed");

A Growing and Evolving Module Structure

So far we have been talking about an application with many modules, but what happens when your designer wants a settings page that acts totally different from your task list? Or user authentication/management? Or making a full blown event scheduler where your task management system becomes a very small piece of a much bigger puzzle?

Marionette still has your back for a very simple reason–there is very little difference between a module and an application. The implication of that is pretty big. It means what starts out as an application, then moved to an application with modules, can then evolve into an application manager with many sub applications or modules which themselves have modules. This means that as the outside world grows, your cohesive families of objects don’t have to. At each level of granularity the communication mechanisms stay the same and you can easily move these trees of functionality around since they don’t depend on anything outside of themselves.

The other thing to keep in mind is that what we are building is high level abstractions of ideas which communicate with each other by passing messages. If this sounds familiar, it is. It’s what Object Oriented Design is all about! The only difference here is that we are moving to a higher level of abstraction then classes, but at that level all of the same considerations, benefits, and tradeoffs still apply.

Some final notes

This is an extremely long blog post, but some final points to keep in mind as you go off and start designing composable, modular applications

  • It is usually not a good idea to bind module communication directly to its lower level constructs, but to have a layer in between. If it requires the orchestration of several objects, I would recommend module “services” that only mediate those orchestrations. Even if it is relatively simple, I would still use a “Controller” class (Derick does this a lot in his BBCloneMail sample application)
  • Eventing provides a very high level of decoupling, at the cost of making code execution hard to follow. Decoupling is great, but when you take it too far you end up with as big a mess (or worse) then not decoupling at all. The only way to get a right feel for the level of granularity required for the task at hand is with practice. Always asking yourself “What about that worked? What didn’t?” when you complete a task.
  • Object Oriented Design is a fantastic fit for UIs, much better then almost any other application of its ideas that I have ever seen. If you want to build complex interfaces, the best thing you can do is beef up on your object oriented design skills.
  • Start with a single module, and grow from there. This system is designed to be easy to move these pieces around. A very common mistake is over design at the start and introduce complexity by applying these concepts rather then reducing it.

Viewing latest article 1
Browse Latest Browse All 3

Trending Articles