Mediator Pattern applied to Javascript

2007 October 19
by Paul Marcotte
In a previous post about JSLint AIR, I introduced my concept of an "Application" object that was responsible for registering and initializing individual components. In turn, each of these components was responsible for a specific piece of the application, either a UI element, generating a report, or interfacing with the Adobe AIR API. At the time, I thought that it was a good way to initialize different Ext JS components, encapsulate and organize my code. I've since refined that object slightly as I have learned that I was actually employing the Mediator design pattern. Mediator is described in the GoF book, Design Patterns: Elements of Reusable Object-Oriented Software as a behavioural pattern with this intent, "Define an object that encapsulated 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." If you're a patterns guru, that description might make complete sense, but I find a diagram and examples much easier to comprehend.The diagram below is a general representation of the interaction between a mediator and colleagues. Each colleague stores a reference to the mediator and the mediator stores a reference to each colleague. Colleagues are unaware of each other and messages are broadcast through the mediator. A real world examples are an air traffic control tower. A plane is unaware of other aircraft in the and all communication is routed through the control tower. Two of the benefits of using the Mediator pattern are that that it decouples colleagues and simplifies object interaction. Instead of using the Observer pattern to explicitly set many-to-many listeners and events, Mediator allows you to broadcast events globally across colleagues. The biggest drawback of using Mediator pattern is that due to the centralized control, the mediator can become difficult to manage. In the case of this implementation, event broadcasting is greatly simplified which keeps the mediator managable. For a more concrete example, I've included a diagram of the colleagues and mediator used in JSLint AIR below. The Layout object encapsulates and initializes the creation of an Ext.BorderLayout. The Menu object does the same for an Ext.Toolbar and so on. Each object broadcasts changes in state through the mediator. Wrapping Ext JS components inside another object might seem like overkill, especially for an app with only a few UI elements. As I mentioned before, the original intent was to manage the initialization of multiple UI elements. When I added event broadcasting, I realized that I could announce the event rather than explicitly call it on a component object. Finally, after reading more about the pattern, I decided to not only broadcast the event, but send the broadcasting object along as the event source. Here's the current code for my Javascript Mediator object.
var Mediator = function () {
    // private
    var components = {};
    // public
    return {
        /*
         * Initialization method       
         */
        init : function ()
        {
            /*
             * Broadcast 'init' event to initialize all components
             */
            this.broadcastEvent('init',this);
        },
       
        /*
         * Adds a component to the components associative array
         *
         * @param     name {string}    the key reference for the component
         * @param     component {object}    an function literal object          
         */
        addComponent : function (name, component)
        {
            // ensure that component is not already in associative array
            for (var k in components)
            {
                if (components[k] === component)
                {
                    break;
                }   
            }
            components[name] = component;
        },
       
        /*
         * Removes a component from the associative array
         *
         * @param     name {string}    an function literal object
         *            
         */
        removeComponent : function (name)
        {
            for (var k in components)
            {
                // if component match found, remove it
                if (components[k] === name)
                {
                    delete components[k];
                }
            }
        },
       
        /*
         * Provides a general event broadcast implementation
         *
         * @param    event {string} the event to broadcast
         * @param    source {object} the source object broadcasting the event
         */
        broadcastEvent : function (event,source)
        {
            for (var k in components)
            {
                // if component implements the event as a function, call it
                if (typeof components[k][event] === 'function')
                {
                    components[k][event](source);
                }
            }       
        }      
    };
}();
Here is the code for the Layout object.
Mediator.addComponent('Layout', function () {
    // private
    var mediator= null;
    var setMediator = function (m) {
            mediator = m;
        };
    var getMediator = function () {
            return mediator;
        };
    var layout = null;
    var render = function () {
       layout = new Ext.BorderLayout(document.body, {
                north: { split: false, initialSize: 29 },
                center: { titlebar: false, tabPosition: 'top' }
            });
            layout.beginUpdate();
            layout.add('north', new Ext.ContentPanel('north-div', { fitToFrame: true, closable: false }));
            layout.add('center', new Ext.ContentPanel('center-form', { fitToFrame: false, title: 'Form', closable: false }));
            layout.add('center', new Ext.ContentPanel('center-report', { fitToFrame: false, title: 'Report', closable: false }));
            layout.add('center', new Ext.ContentPanel('center-listing', { fitToFrame: false, title: 'Listing', closable: false }));
               layout.getRegion('center').showPanel('center-form');
            layout.endUpdate();   
        };
    // public
    return {
        init : function(m) {
               setMediator(m);
               render();
        }
    }
}());
The process is to add components to the Mediator object and the initialize everything like so. Ext.onReady(Mediator.init, Mediator); This sets a reference in the component objects which can then broadcast events to colleagues sending itself as the event source. So, within one of the components I might have a snippet along the following. getMediator().broadcastEvent('eventName',this); As you can see this certainly simplifies object interaction and keeps components loosely coupled which was my original goal.