Seat Designer (part 2): Design patterns

What’s it take to be a frontend engineer? It isn’t jQuery or a bad ass library that has promised to change your life. It also isn’t the ability to write “pure” javascript. What it really takes is understanding all the design patterns at your disposal, and how they fit together to power a munitions depo of frontend weapons. You’re about to enter a war zone, and you need heavy artillery.

While developing seat designer we used a lot of patterns and tactics to keep our code base clean and happy. We learned a lot from Addy Osmani’s book on design patterns useful for frontend.

Here are 3 of the most powerful patterns we used during the creation of seat designer:

Mixins

We used this great library for mixins called Backbone Advice. Advice allowed us to add functionality safely to classes. For instance, if wanted to interact with a shape in this paradigm we would mix functionality in like this:

return TablesAndChairsView.extend({...})
  .mixin(DoubleClickToEditMixin)
  .mixin(SelectableMixin)
  .mixin(RotatableMixin)
  .mixin(MoveableMixin)
  .mixin(CloneableMixin)
  .mixin(ContextMenuMixin);

Each of these mixins added functions to the TablesAndChairsView class, added events to the events object and wrapped functions like `initialize` and `render` all without conflicting with each other.

Here is an excerpt from our DoubleClickToEditMixin:

define(function(require) {
    'use strict';

    var _ = require('underscore');

    function addMixin() {
        this.addToObj({
            events: {
                'dblclick': 'handleDoubleClick'
            }
        });

        this.setDefaults({
            handleDoubleClick: function() {
                this.app.execute('navigate:edit', this);
                this.app.trigger('closeAllControls');
            }
        });

        this.after('initialize', function() {
            _.bindAll(
                this,
                'handleDoubleClick'
            );
        });
    }

    return addMixin;

});

This excerpt shows how we added an event without clobbering the event object, and how we executed some extra code right after the object was initialized.

Components

components Components pulled us out of a strict MVC (Model/View/Controller) mindset. MVC while useful doesn’t really lend itself well to front-end applications. What you really want are components with a more loose structure composed of models, views, and other utilities. Components are everything. They are both the help system and the individual tooltips. They are both a section of seats and the individual chairs.

This amazing pattern actually gave us a lot of freedom and simplified lots of constant decision making. If you felt like asking “where does this feature go?” The answer was always “it is a new component.” If the question was “where do I put this template?” Then the answer was, “what component does it belong to?” If the question was how do I reuse this feature. The answer was, “oh just initialize that component again.”

Another side benefit was that some components had an on or off state. Using MarionetteJS modules we were able to flip components on or off. The help component uses this concept to show help tooltips only when users haven’t been back in a while. We also used this concept while putting the application into a focus mode. While in this mode the component was on, and once the user wanted to leave, we simply flipped the component off.

Request/Response

Often, the furthest reaches of your app need access to a something that happens on initialization. You may have previously used a form of dependency injection that passed objects all the way down the pipeline. This is fairly exhausting and results in really high coupling.

Using Backbone.Wreqr, we were able to register objects into our application’s core on startup, and later request those resources.

For instance if we want to define an “Event” object that contains the title, date, and location of an event we may have previously just passed that around. With Wreqr and Request/Response we would do the following:

core/app.js

app.reqres.setHandlers('event', function() {
    return event; // either passed in on init or via AJAX
});

Elsewhere we might do something like this:

define(function(require) {
var Handlebars = require('handlebars'),

  app = require('core/application').getInstance(),
  event = app.reqres.request('event');

  return Backbone.View.extend({
    render: function() {
      return Handlebars.compile('<h1>{{title}}</h1>')(event);
    };
  });
});

No more passing around the event object all the way down stream. Here we simply set up our app to have some “requestable” objects and access them as necessary.

Other notable patterns included mediator, actor, and factory pattern. If you’re dealing with some amounts of spaghetti and excessive context switching in your current code base it may be worth introducing new patterns into the mix.