Mediator Pattern applied to Javascript

Design Patterns , Javascript , Ext JS Add comments
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.

6 responses to “Mediator Pattern applied to Javascript”

  1. H Archer Says:
    Very nice deisgn pattern for ExtJs. I've used your method with success. But, now that I'm using Ext2, I'm not sure if I need to store all the components within a mediator, since all my components extend the observer class, and hence it can manage the event. I might be wrong.

    Two other issues:

    1. You cannot have two classes/components with the same event/function name. All events/functions must have unigue names, which is not ideal.

    2. You cannot pass arguments to your function call. I had to customise your Mediator method broadcastEvent to do this. Would like to see how u normally pass values with your event calls, when needed.

    It's good that you pass the scope now. Makes sence also.

    I added this method to the Mediator:

    getComponent : function (name)
    {
       return components[name];
    }

    With this, you can get a reference of a component and call its functions. Handy.

    You're work is great...Keep it up and thanks again :)
  2. Paul Marcotte Says:
    Hi Hamad,

    Thanks for the kind words. Always appreciated!

    I struggled with using the ExtJS observer pattern. For some reason, it just wasn't clicking.

    The big difference between this Mediator implementation and an observer/observable pattern is that you have to register the observer for specific events. In a complex system this can add a lot of code. The simple Mediator has the advantage of broadcasting messages to all "listeners", regardless of subscription. The disadvantage is that the event names are not re-usable.

    I toyed with adding a getComponent() method exactly like the one you're using, but felt that it would break encapsulation since the colleagues should know nothing about each other.

    If you want to pass arguments to a listener through the broadcastEvent() method you could modify it like so:

    broadcastEvent : function (event,source,args)
    {
    var args = (arguments.length == 3) ? args : {};
             
             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,args);
    }
    }
    }

    That's off the top of my head, so no guarantee it work, but the idea is that if a third argument is passed, then use that argument. If not use an empty object literal.

    If you want to give that a whirl and let me know if it works, I'd appreciate it. :)

    Cheers!
  3. H Archer Says:
    Hi, that's very similar to what I do for passing arguments:

       broadcastEvent : function (event, paramObj)
       {
          paramObj = (typeof paramObj == 'undefined') ? {} : paramObj;
          for (var k in components)
          {
             // if component implements the event as a function, call it
             if (typeof components[k][event] === 'function')
             {
                components[k][event](paramObj);
             }
          }
       }

    I agree with what you say regarding the added getComponent method and encapsulation. However, back to my previous point #1 and you comment "disadvantage is that the event names are not re-usable...". I had 3 different components that required an uploader to get displayed on clicking of the upload button. So I needed to have a method named getUploader on all three components. Now as soon as I fire this event (getUploader), this method will get called three times from the 3 components. So I had to have a way of targeting a component and firing an event from there and only there. That's when I realised I could add getComponent and use it to fire an event for a specified component.

    I'm sure this is a better solution that retains encapsulation. Maybe sending a reference for the target component with the event call, same as we are doing for the arguments. But that would break the mediator pattern. Although, what if we have it as a special case for methods that need sharing/reusing only...

    I'm starting a new ext2 cms need to make a decision as to using your pattern with those improvements that we are discussing, or using an ext modular pattern, more on it here:
    http://extjs.com/forum/showthread.php?t=26728

    H Archer
  4. H Archer Says:
    [EDIT] correction for above comment,

    I mean to say:
    "I'm sure THERE is a better solution that retains encapsulation...."
  5. Paul Marcotte Says:
    Hi Hamad,

    Unfortunately, there's no "one true way" for designing an application, so you you really need to stick with what works for you.

    I haven't had a lot of time to work with ExtJS 2.x yet, but when I do, I'll definitely investigate alternatives to this Mediator pattern.

    I like your argument passing code better than mine, btw. Good work!
  6. HB Says:
    Thanks for this post, I've been using your Mediator (or some variation thereof) for over a year. I've since made some modifications based on the above comments and my own needs: http://bit.ly/dMdMu

Leave a Reply



Powered by Mango Blog. Design and Icons by N.Design Studio