Skip to main content

https://mojdigital.blog.gov.uk/addendum-modular-javascript-with-heisenberg/

Addendum - Modular JavaScript with Heisenberg

This page is an addendum to a post regarding the MOJ Front-end team's strategy for organising modular JavaScript.

I mentioned that we use (some of) the Heisenberg pattern developed by Ben Howdle - here is what we actually use from that, and how it works.

Firstly here is a typical list of JS file includes, which I'll explain afterwards:

<script src='/assets/moj.js'></script>
<script src='/assets/example-module-one.js'></script>
<script src='/assets/example-module-two.js'></script>
<script src='/assets/main.js'></script>

Or, as it would appear in a Rails manifest:

//= require moj
//= require example-module-one
//= require example-module-two
//= require main

The moj.js and main.js files are the functional bookends of the system, and remain unchanged from project to project. The other files are naturally the modules we're including, of which there could be any number.

Here is the entirety of moj.js:

/*jslint browser: true, evil: false, plusplus: true, white: true, indent: 2 */
/*global $ */

(function(){
  "use strict";

  var moj = {

    Modules: {},

    Utilities: {},

    Events: $({}),

    init: function(){
      var x;

      for( x in moj.Modules ) {
        if( moj.Modules.hasOwnProperty( x ) ){
          moj.Modules[ x ].init();
        }
      }
    }

  };

  window.moj = moj;
}());

This is taken directly from Heisenberg, with the addition of the .hasOwnProperty check for extra robustness. In our current, quite minimal, usage we're not putting anything into Utilities or Events - we're just adding module files into Modules, and preparing an init function which will iterate through each loaded module and fire the init function of each - but we're leaving Utilities and Events in there for probable future usage.

The initial comment block is directives for JSLint. (If you're not running your code through JSLint, then Douglas Crockford will come to your house and disturb you in the night by reading from his book under your window.) We set browser to true, since we are writing code for use in browsers only, we set evil to false, since you should never use eval (Crockford will pursue you into the afterlife if you do), plusplus is set to true since it's just plain handy and the downsides are negligible, we set white to true since we have our own views on whitespace (more on that later), and we set indent to 2 spaces because that looks tidier when your project is Ruby and HAML, and 4 spaces just looks ludicrous nowadays. Neatness FTW. Oh, and we always include jQuery before we start, so we let JSLint know about the $ global variable.

OK, so that's moj.js - here is main.js:

/*jslint browser: true, evil: false, plusplus: true, white: true, indent: 2 */
/*global moj, $ */

$(function() {
  moj.init();
});

Yep, it just does one thing - fire the init method of the global moj object, once the DOM is ready. No explanation needed here.

So those two are the bookends - here is an actual module from a current project:

/*jslint browser: true, evil: false, plusplus: true, white: true, indent: 2 */
/*global moj, $ */

moj.Modules.documents = (function() {
  "use strict";

  var init,
      cacheEls,
      bindEvents,
      $form,
      clearBtns,
      resetForm;

  init = function() {

    cacheEls();
    bindEvents();
    
  };

  cacheEls = function() {
    $form = $( 'form' ).eq( 0 );
    clearBtns = $( '.button.clear', $form );
  };

  bindEvents = function() {
    $( clearBtns ).each( function() {
      $( this ).on( 'click', function() {
        resetForm();
      } );
    } );
  };

  resetForm = function() {
    var $this;
    $( 'input[type="text"]', $form).val( '' );
    $( 'select', $form ).each( function() {
      $this = $( this );
      $this.find( 'option:selected' ).removeAttr( 'selected' );
      $this.find( 'option' ).eq( 0 ).attr( 'selected', 'selected' );
    } );
    $( '.type-filter', $form ).find( 'input[type="radio"]' ).eq( 0 ).trigger( 'click' );
  };

  // public

  return {
    init: init
  };
}());

This is the structure of a module in Heisenberg. We declare the module in the Modules namespace in its own functional scope, "use strict", declare our vars and functions, then define our init function which caches elements and binds events first, and would then proceed to any other initialisation function calls we might need. This is a simple module, but it is a real one taken from a live project and serves as a decent example. The operational scope of this module is a single page, so all its methods are private and the only thing exposed publicly is the init method so that moj.js can fire it.

This structure allows us to go and write our modules separately from all other files and drop them into the project by simply including the file in the JS includes between moj.js and main.js. Any public methods would be declared in the return for other modules to access as per usual.

I mentioned earlier that we have our own ideas about whitespace, but I think this has gone on long enough for now, so that can be saved for another post.