Geeks With Blogs
Liam McLennan hackingon.net

Increasingly I find myself building web applications that rarely, if ever, reload the page. Ajax is used to communicate with the server and to update parts of the page as required. After much trial and error I have come upon a technique for organising the javascript on such pages.

Consider an application that has three buttons: red, yellow and green. When one of the buttons is clicked an rectangle is coloured according to the selection.

The UI design will be as follows:

mockup

Here is the demo.

The basic steps in my approach to javascript organisation for such a page are:

  1. Define a namespace for my custom javascript. In this case ‘TrafficLight’.
  2. Define an ‘initialize’ method within the top level namespace and call it from jQuery’s document ready event handler.
  3. Within the initialize method wrap each logical screen component within a javascript object. In this case I will have ‘buttons’ for the buttons and ‘rectangle’ for the rectangle.
  4. Use a ‘screen activator’ to configure a generic event aggregator so that the screen components do not have to be tightly coupled.

This might seem like overkill for such a simple application but I am trying to demonstrate a technique that considerably reduces complexity for more complicated UIs.

Here is the html for the page. Note the document ready event handler and the call to TrafficLight.initialise().

<html>
  <head>
    <link rel="Stylesheet" type="text/css" href="traffic_light.css">
  </head>
  <body>  
    <div id="button_container">
      <input type="button" value="Red" class="colour_button"/><br/>
      <input type="button" value="Yellow" class="colour_button"/><br/>
      <input type="button" value="Green" class="colour_button"/>
    </div>
    <div id="coloured_rectangle">
      
    </div>
    <div id="event_aggregator_element"/>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <script type="text/javascript" src="TrafficLight.js"></script>
    <script type="text/javascript">
    
      $(document).ready(function() {
        TrafficLight.initialise($('#button_container'), $('#coloured_rectangle'), $('#event_aggregator_element'));
      });
    
    </script>    
  </body>
</html>

The TrafficLight.js file:


// define the namespace
var TrafficLight = TrafficLight || {};

TrafficLight.initialise = function(buttonsWrapperSet, rectangleWrapperSet, aggregatorWrapperSet) {

  // create an event aggregator. 
  // the array lists the valid events for this screen.
  var aggregator = Aggregator.constructor(aggregatorWrapperSet, ['ColourSelected']);
  
  // create objects for the major screen components
  var buttons = TrafficLight.buttonsConstructor(buttonsWrapperSet, aggregator);
  var rectangle = TrafficLight.rectangleConstructor(rectangleWrapperSet, aggregator);

  // create a screen activator to connect the various components
   var screenActivator = TrafficLight.screenActivatorConstructor(buttons, rectangle, aggregator);
  screenActivator.activate();

};

TrafficLight.screenActivatorConstructor = function(buttons, rectangle, aggregator) {
  var that = {};
  that.activate = function() {
    // bind the 'ColourSelected' event to the rectangle object
    aggregator.bindToColourSelected(rectangle.handleColourChange);
  };  
  return that;
};

TrafficLight.buttonsConstructor = function(buttonsWrapperSet, aggregator) {
  var that = {};  
  
  // configure event handlers for the individual buttons
  $('input', buttonsWrapperSet).click(function() {
    var selectedColour = $(this).val();
    // raise the 'ColourSelected' event.
    aggregator.triggerColourSelected([selectedColour]);
  });
  
  return that;
};

TrafficLight.rectangleConstructor = function(rectangleWrapperSet) {
  var that = {};

  that.handleColourChange = function(event, new_colour) {
    rectangleWrapperSet.css('background-color', new_colour);
  };
  
  return that;
};


var Aggregator = {

    constructor: function(wrapperSet, supportedEvents) {
        if (wrapperSet.length > 1) {
            alert("more than one object selected as event aggregator.");
            return;
        }
        var that = {};

        $.each(supportedEvents, function(index, eventName) {
            that['bindTo' + eventName] = function(callback, data) {
                wrapperSet.bind(eventName, data, callback);
                Debug.Log('Callback registered for ' + eventName);
            };
            that['trigger' + eventName] = function(extraParameters) {
                wrapperSet.trigger(eventName, extraParameters);
                Debug.Log('Triggered ' + eventName);
            };
        });
        return that;
    }

};

var Debug = Debug || {};

Debug.Log = function(message) {

    if (window.console && window.console.log) {
        window.console.log(message);
    }
};
Posted on Wednesday, August 25, 2010 10:43 AM | Back to top

Copyright © Liam McLennan | Powered by: GeeksWithBlogs.net