Skip to content
Nathan Davis Olds edited this page Dec 28, 2013 · 2 revisions

Coreisma is a lightweight module manager for javascript. It provides a clear way to encapsulate javascript snippets into modules, facilitates communication between modules through event notifications, and exposes shared code in through extensions.

Modules

Modules are the central use of coreisma. A module implements behaviors associated with part of a web page.

A module is defined with the addModule function. The addModule function registers the module with the Hub (more on the hub later).

Coreisma.addModule("Unique Module Name", {
  init: function() {
    console.info("Is called when the hub starts, usually on page load.")
  },
  shutdown: function() {
    console.info("Is called when the hub stops, but will rarely be used.")
  }
});

If you have Coreisma loaded in a browser, you can run the code above in the console. After entering Coreisma.start(); into the console, you will see the expected message "Is called when the hub starts, usually on page load". It will also display the correct message with Coreisma.stop();. So, when Coreisma is started and stopped it will call the init and shutdown functions respectively.

Now this isn't all that powerful. It is just a glorified on-page-load function. However, it does provide a systematic way to organize javascript code. Here is a example for the autolink.js file in the portfolios assets folder.

$(function() {
  $('#letters, #documents').on('click', '[data-url]', gotoPage)

  function gotoPage() { window.location = $(this).data('url') }
})

Easily becomes:

Coreisma.addModule("Auto Link", {
  init: function() {
    $('#letters, #documents').on('click', '[data-url]', gotoPage)

    function gotoPage() { window.location = $(this).data('url') }
  }
});

Most of the time our modules are more complex. Instead of passing a static json object we can use a function. Return the correct json and we are free to name and define functions inside our module without worry of conflicting with other modules. In this way we have a clean way to write robust modules.

Coreisma.addModule("Auto Link", function() {
  function gotoPage() {
    window.location = $(this).data('url')
  }

  function onPageLoad() {
    $('#letters, #documents').on('click', '[data-url]', gotoPage)
  }

  return {
    init: onPageLoad
  };
});

Here's a more complex example:

Coreisma.addModule("Service Source", function(core, hub) {
  var updateServiceSource = function(data) {
    if (service_source_key = data.service_source_key) {
      $(".service-source-selection").val(service_source_key);
    }
  };

  var updateNotes = function() {
    hub.broadcast('note.added');
  }

  var changeServiceSource = function(ev) {
    ev.preventDefault();

    var $select = $(this),
        $form = $select.closest('form'),
        url = $form.attr('action');

    core.ajax({
      url: url,
      method: 'put',
      data: $form.serialize(),
      success: updateNotes
    })
  };

  var init = function() {
    $('.service-source-selection').on('change', changeServiceSource)
    hub.listen("service_source.change", updateServiceSource);
  };

  return {
    init: init
  };
});

This module is slightly more interesting. On init, it sets up two events. The first is a jQuery onChange event handler for the .service-source-selection select box. The second line sets up a listener through the hub for 'service_source.change'. In both these cases the function passed as the second argument will be called when the event happens.

Notice two arguments that have been added to the function definition called the core and the hub.

The Hub

The hub handles the communication between modules. When an event occurs, the hub is used to broadcast the event to all the modules set to listen for that event. Each module handles the event in its own appropriate way. No module should directly call another module. Instead, use the listen and broadcast functions on the hub.

At the end of previous section, the example shows a module that reacts to service_source.change and uses the data (in json format) to update an element on the page. A service source change occurs someplace else; it really doesn't matter where it changes, but in this application it can change on the letter popup.

This example also shows an example of a broadcast. After the ajax updates the service source on the server (and also adds a note), then the module will broadcast that a note was created. Another module (which is listening for the note.added event) will fire.

Coreisma.addModule("Notes updater", function(core, hub) {
  var updateNotes = function(data) {
    var appendNotes = function(html) {
      $('#notes .note').remove();
      $('#notes').append(html);
    }

    core.ajax({
      url: core.meta.portfolioNotesUrl,
      type: 'get',
      success: appendNotes
    })
  }

  var init = function() {
    hub.listen("note.added", updateNotes);
  };

  return {
    init: init
  };
});

This is a simple module with a bullying approach to updating notes. On every note.added event, it will retrieve all notes and rebuild the entire note list.

If you are wondering if something is off a bit in these examples, I agree with you. First, why does the Service Source module know that a note was created. It would be clearer to have the event that was fired from the Service Source module as 'service_source.change'. The note updater would then listen for all the events that could possibly add a note. Here is an updated version of the two modules.

Coreisma.addModule("Service Source", function(core, hub) {
  var updateServiceSource = function(data) {
    if (service_source_key = data.service_source_key) {
      $(".service-source-selection").val(service_source_key);
    }
  };

  var changeServiceSource = function(ev) {
    ev.preventDefault();

    var $select = $(this),
        $form = $select.closest('form'),
        url = $form.attr('action');

    core.ajax({
      url: url,
      method: 'put',
      data: $form.serialize(),
      success: function() {
        hub.broadcast("service_source.change");
      }
    })
  };

  var init = function() {
    $('.service-source-selection').on('change', changeServiceSource)
    hub.listen("service_source.change", updateServiceSource);
  };

  return {
    init: init
  };
});

Coreisma.addModule("Notes updater", function(core, hub) {
  var updateNotes = function(data) {
    var appendNotes = function(html) {
      $('#notes .note').remove();
      $('#notes').append(html);
    }

    core.ajax({
      url: core.meta.portfolioNotesUrl,
      type: 'get',
      success: appendNotes
    })
  }

  var init = function() {
    hub.listen("note.added", updateNotes);
    hub.listen("service_source.change", updateNotes);
  };

  return {
    init: init
  };
});

Core Extensions

Extensions expose standard helper functions to modules. Commonly used patterns like ajax, templating, and building urls are provided to each module through the core. For instance, the Coreisma.ajax extension is a simple wrapper around jQuery's ajax call, but by using the core function we can begin to also accept additional parameters (like something that automatically fires an event when successfully completing).

A benefit of standardizing through extensions is being able to optimizing or change the way the application performs at a later time. Let's say we start to use jQuery's each method but later prefer the each method in lodash, we could switch in one place and our whole app will break...ahem...I mean perform better.

If you would like to look at how extensions are used, there are lots examples in the code. In fact, Coreisma itself (modeled from jQuery) begins with a single method to extend itself. Everything from there on I would consider an extension. Here is an example of an extension which uses the information in the meta tags to build useful urls.

Coreisma.addExtension("MetaLinks", function(core, hub) {
  var metaContent = function(metaName) {
    return $("meta[name='" + metaName + "']").attr('content')
  }

  return {
    meta: {
      portfolioId: metaContent('portfolio-id'),
      portfolioUrl: metaContent('portfolio-url'),
      portfolioNotesUrl: metaContent('portfolio-url') + "/notes"
    }
  }
});

Clone this wiki locally