Large JavaScript Applications with a modular approach

2013-12-01

While working in grown projects you may try to find a solution to structure your JavaScript in a maintainable way. One possibility is to use a MV* framework like Backbone, Ember or Angular. A different/extended solution tries to be modular. With the Mediator Design Pattern this is easily achieveable.

I came to this approach by reading Addy Osmani's article about "Patterns For Large-Scale JavaScript Application Architecture".

What is the Mediator Design Pattern?

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

from the book Design Patterns. Elements of Reusable Object-Oriented Software.

That means you have your modules, wich will publish and subscribe to events without knowing anything about each other. This way you can also have subscribers to events which will never be published and publishers which will not be subscribed to. It will not break your application if that happens.

How to work with Mediator Design Pattern in JavaScript

Because it is good to not reinvent the wheel everytime you code something, download/clone mediator.js from Github. It is a free MIT licensed library with the Design Pattern already implemented.

Let me show you an example usage with a simple module that calculates the square of a rectangle. Let us start with the following HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Mediator Design Pattern with JavaScript</title>
  </head>
  <body>
    <script src="script/vendor/mediator/mediator.min.js"></script>
    <script src="script/base.js"></script>
    <script src="script/math/geometry/rect.js"></script>
    <script src="script/io/input.js"></script>
    <script src="script/io/output.js"></script>
    <script src="script/app.js"></script>
  </body>
</html>

Before we can use Mediator.js, we have to instantiate it:

// in script/base.js
var mediatorInstance = new Mediator();

Now we make a math geometry rectangle module which calculates two numbers to a square:

// in script/math/geometry/rect.js
(function(mediator) {
  "use strict";

  var getSquare = function(length, height) {
    return length * height;
  };

  mediator.subscribe(
    'math:geometry:rect:getSquare',
    function(length, height, square) {
      square.result = getSquare(length, height);
    }
  );
}(mediatorInstance));

Let the user enter the length and height of the rectangle and make an output:

// in script/io/input.js
(function(mediator) {
  "use strict";

  var getAnswer = function(question) {
    return prompt(question, '');
  };

  mediator.subscribe(
    'io:input:getAnswer',
    function(question, answer) {
      answer.result = getAnswer(question);
    }
  );
}(mediatorInstance));
// in script/io/output.js
(function(mediator) {
  "use strict";

  var outputByAlert = function(msg) {
    alert(msg);
  };

  mediator.subscribe(
    'io:output:alert',
    function(msg) {
      outputByAlert(msg);
    }
  );
}(mediatorInstance));
// in script/app.js
(function(mediator) {
  "use strict";

  var data = {
    result: 0
  },
  length = 0,
  height = 0,
  square = 0;

  mediator.publish(
    'io:input:getAnswer',
    'Enter the length of the rectangle',
    data
  );

  length = data.result;

  mediator.publish(
    'io:input:getAnswer',
    'Enter the height of the rectangle',
    data
  );

  height = data.result;

  mediator.publish(
    'math:geometry:rect:getSquare',
    length,
    height,
    data
  );

  square = data.result;

  mediator.publish('io:output:alert', square);
}(mediatorInstance));

Conclusion

That is it. So we have now 3 little modules and some kind of controller, which puts everything together. If we forgot to include for example the output.js we would simply get no output, but also no error. This is a good thing if you have a really large application and misspelled for example an event, because only that one little part will break and not the complete functionality. Of course you have to check now a little bit more if you really included every needed file for your app, but if you use something like Grunt to make a minified version of your files and bind them all together, then it should be no problem.

Debugging may become a little bit more complicated with the Mediator Design Pattern, but you can have now a really modular way to handle all your JavaScript.