winfred.nadeau

Great software speaks for itself.

Javascript Function Wrapping

originally written August 02, 2012

Straight from the “I don’t know what I’m doing” department.

The Setup

Around a month ago, I started working on a new javascript library to scratch a personal itch. Essentially, I’m attemping (hoping) to build a framework designed specifically for whiteboard applications, as I’ve been awfully disappointed with the existing offerings. Of course, if the end result is anything worth sharing, this framework will absolutely be open sourced.

With the architectural design debate going round in my head, the module dedicated to StrokeActions (such as rotating/enlarging a brush stroke) seemed like a good candidate for a Functional arrangement of logic rather than an Object Oriented arrangement. That is to say, it seemed like a lot of unnecessary boilerplate code to set up prototypal inheritence and instantiation for the wide array of potential StrokeActions in this module that would be shortlived at runtime.

From the perspective of a developer building off the framework, I want it to be as easy as possible to write custom StrokeActions that can be applied to their brush strokes. Here’s a quick look at what the initial ‘focus’ StrokeAction looked like (a bit simplified). It was designed to make a brush stroke appear ‘focused’ to the user by applying some CSS styles, all very standard for a whiteboard application.

whiteboard.StrokeAction.focus =  {
  invoke: function(event) {
    //unfocus the currently focused stroke
    var focusedStroke = whiteboard.StrokeAction.focusedStroke();
    if (focusedStroke) focusedStroke.removeClass('whiteboard-focused');
    //focus this stroke
    event.currentTarget.addClass('whiteboard-focused');
    //listen for a loss of focus
    whiteboard.StrokeAction.enableDocumentClickCatching(whiteboard.StrokeAction.focus.complete);
  },

  complete: function(event) {
    if (!event.target.isContainedInElementOfClass('whiteboard-focused')) {
      var stroke = whiteboard.StrokeAction.focusedStroke();
      
      stroke.removeClass('whiteboard-focused');
      whiteboard.StrokeAction.disableDocumentClickCatching(whiteboard.StrokeAction.focus.complete);
    }
  }
};

Basically all StrokeActions respond to the invoke method in their namespace (method name subject to change), so a designer/developer can connect a brush’s tools to an action directly in the brush’s HTML markup. (such as a ‘rotate’ button in the markup that will automatically hook in to whiteboard.StrokeAction.rotate.invoke upon being clicked)

I really like this style of ‘setup’ and ‘teardown’ functions that take a DOMEvent and tell the browser how to accurately perform a certain action on a brush stroke. No instantion, just logic.

The Problem

Shortly after getting a basic sandbox with brushes/strokes/actions running in a browser, I realized that an event-driven architecture was the only viable architecture for a library like this (in hindsight it’s obvious). I wanted each of these functions to automatically emit internal events without the need for a developer to manually call an emit method. In OO, I might put that logic in the super class and force API consumers to remember to call some kind of ‘super’ in their subclasses. (* cough * like iOS/Cocoa) But that just sucks.

The solution

I’ve seen this pattern in many different JS libs out there that seemed like it might provide a place for this shared logic, so I wrote a basic “extend” function in the StrokeAction module that will wrap these actions with emitters and keep specific StrokeAction implements clutter-free.

Here’s what the new focus action looks like.

//I'm using _ and $ as module shortcuts in this scope for brevity when accessing public module methods
//This isn't underscore and jQuery...
whiteboard.StrokeAction.focus = (function(_,$){

  return $.extend({
    invoke: function(event) {
      var focusedStroke = $.focusedStroke();
      if (focusedStroke) focusedStroke.removeClass('whiteboard-focused');

      event.currentTarget.addClass('whiteboard-focused');
      $.enableDocumentClickCatching($.focus.complete);
    },
    //complete: omitted for brevity
  });

})(whiteboard, whiteboard.StrokeAction);

The major difference is that I’m using an .extend function, so this is where the magick happens.

whiteboard.StrokeAction = {
  extend: function(actions){
    var strokeAction = {};

    for(var action in actions) {
      /**
       * This is where the actions steps are wrapped with event emission.
       * Perhaps this is a good place for other kinds of hooks to tie in
       *  (before/after)
       */
      strokeAction[action] = (function(action, fn){
        return  function(DOMevent){
          var event = {
            stroke:	_strokeForElement(DOMevent.target),
            e: DOMevent
          };
          fn.apply(strokeAction[action], arguments);
          $.emit.apply(this, [action, event]);
        };
      })(action,actions[action]);
    }

    return strokeAction;
  }
  //rest of StrokeAction module omitted
}

Ignoring the structure of event names (omitted for clarity), this extend function simply wraps each action in a new function (and closure) that also emits the action’s event.

Just like that, we have event emission applied to all of the previously written StrokeActions without cluttering any of their logic.

Target Locked