For the past couple of months I have been experimenting with a new architecture for the WYMeditor semantic XHTML editor. When I was implementing WYMeditor v0.5 in SkyBlueCanvas, I found that the existing architecture was a bit limiting and would not allow me to easily add custom buttons and fuctionality (although it is possible to do so). I found myself wanting to add plugins to the base software without needing to alter the existing code in any way.

I decided the ideal architecture is one in which WYMeditor has no knowledge of plugins but rather provided the necessary hooks to attach and trigger plugins on specific state changes or custom events. It occurred to me that what I wanted was an architecture that implements the Observer Pattern so that I could subscribe to various state changes.

I also decided that I wanted to be smart about the design and make it so that other developers could also add additional functionality and not have to worry about collisions between the names of functions and properties used in their plugins and those of my or other plugins. So this requirement called for the ability to define custom name spaces.

This article proposes a new architecture that not only satisfies the requirements described above, but also makes even the core features of WYMeditor into plugins using the same API. The proposed architecture essentially converts WYMeditor into a robust editor plugin engine.

There are several benefits to the architecture which this document describes. First, it makes the base code more maintainable. When bugfixes and upgrades to isolated parts of the code are needed, only part of the code needs to be replaced. Second, it gives third-party developers much more power and flexibility to be able to control the execution of their code. Finally, it allows developers to add much more robust features like database interaction, file uploading and even image editing capabilities. I believe the possibilities become almost endless through the new architecture and API.

It occurred to me after several weeks of working with various designs for the new architecture that this design could be implemented with any jQuery extension, thus turning the extension into a pluggable application built on top of the jQuery JavaScript framework.

This article assumes that you have some familiarity with the Function.prototype concept and the $.extend() function. I will be using a generic application called MyApp to demonstrate the concept.

First, let’s begin by defining a new Object for our application.

if (!MyApp) var MyApp = {};

In JavaScript, the window object is the global scope, so we make the creation of the object conditional so that we do no end up with more than one instance of MyApp within the global space.

Next, I am going to set up the base MyApp object with a few default properties and a base method I will call main.

$.extend(MyApp, {
    target: null,
    events: {
        init   : 'onInit',
        run    : 'onRun',
        finish : 'onFinish'
    },
    main: function(target) {
        var events = MyApp.events;
        var _self  = [this];
 
        this.init(target);
 
        $(MyApp).trigger(events.init, _self);
 
        this.run();
 
        $(MyApp).trigger(events.run, _self);
 
        this.finish();
 
        $(MyApp).trigger(events.finish, _self);
    }
});

In the code above, you will notice that I have three parts which are explained in turn below.

1. The target property

The target will be the DOM element or elements on which my application will operate. If you are familiar with jQuery you know that you specify the target of any action with selectors like this:

$("a").click(function() {...});

In the above example, the target property of MyApp would refer to the “a” element passed to jQuery.

2. The events property

The events in MyApp will be the different states or state changes on which my plugins will be triggered. The number and names of events is up to you. For this demonstration I have kept it simple with init, run and finish events.

3. The main method

The main method is the base function of MyApp. It is possible to create a jQuery extension by creating MyApp as new Function rather than an Object, but using the Object allows me to namespace not only MyApp, but also to use different namespaces for each of MyPlugins within the MyApp namespace.

You will notice in the main function that I am setting a local variable named _self to an array containing a reference to the function itself. This is done to cut down on memory usage by avoiding calling the Array constructor each time the trigger method is called. The gains from this may be slight but since I like efficiency, I think every little bit of gain in execution time is positive gain.

At this point, MyApp does not do anything. I still need to define the three methods that are triggered on each of the state changes. For this demonstration my methods will be very simple: I will have them just append a message to the target of their actions.

MyApp.main.prototype.init = function(target) {
    this.target = target;
    $(this.target).append('MyApp.init Called');
};
 
MyApp.main.prototype.run = function(target) {
    $(target).append('MyApp.run Called');
};
 
MyApp.main.prototype.finish = function(target) {
    $(target).append('MyApp.finish Called');
};

I have used JavaScripts prototype object to add functions within the scope of the main function. This is a cleaner way of doing this:

main: function () {
    var init = function() {
        // code goes here ...
    };
 
    var run = function() {
        // code goes here ...
    };
 
    var finish = funciton() {
        // code goes here ...
    };
}

My code is much cleaner, easier to read and so easier to maintain.

The last step is to add my extension to jQuery:

/* Add the extension to jQuery */
 
$.fn.myapp = function() {
    return this.each(function() {
        new MyApp.main($(this));
    });
};

The code above adds a method called ‘myapp’ to the jQuery.fn array so I can call

$(element).myapp();

and MyApp will be passed the target element or elements found by jQuery’s search of the DOM. jQuery’s this.each method is used so that myapp will be executed on every element found matching my selector. It is important to return the result of the execution to jQuery to insure that jQuery’s method chaining functions correctly.

Now I have a fully functional, albeit a very simple, jQuery extension. Since the purpose of this article is to demonstrate a pluggable jQuery extension, I now need to create my plugin, which I will aptly name MyPlugin.

MyPlugin will be created in similar fashion to MyApp except that I will use a new Function rather than an Object.

First I will create the MyPlugin function which will receive a reference to MyApp as its only argument. By passing a reference to MyApp to the plugin, I make all the properties and methods of MyApp available to MyPlugin. The MyPlugin constructor will extend MyApp with MyPlugin.

/* The Plugin code */
 
var MyPlugin = function(app) {
    $.extend(app, {
        MyPlugin: this.init(app)
    });
};

I now have a MyPlugin namespace within the MyApp namespace. This may seem a bit circular, but it is very flexible and useful. First, in this way I avoid any possible collisions between the methods with MyPlugin and MyApp as well as within other plugins. By extending MyApp in this way, I also make MyPlugin available to other plugins to MyApp. I could feasibly do something like this:

MyApp.MyPlugin2 = function(app) {
    app.MyPlugin1.method();
};

MyPlugin is not yet complete, however, so I will add a simple function to it again using the prototype Object.

MyPlugin.prototype.init = function(app) {
    $(app.target).append('MyPlugin fired');
};

And lastly, I need to tell MyApp when it should run MyPlugin. I will do this using jQuery’s bind function. The bind/trigger combination in jQuery is, in my opinion, one of its most powerful features and the one that makes the architecture described in this article possible.

$(MyApp).bind('onRun', function(e, app) {
    new MyPlugin(app);
});

The jQuery.bind() function takes the following form:

$(scope).bind(event, callback);

The scope is the space in which the event will occur and the callback is the code that will be executed when the event occurs. It is important that the specified event actually exists or occurs within the specified scope. If you attempt to call the callback on an event that does not exist in the specified scope, nothing will happen or, you may get unexpected results if an event by the same name exists but in the wrong scope.

Now if we refer back to the base code for MyApp, you will notice the events I set up:

$.extend(MyApp, {
    target: null,
    events: {
        init   : 'onInit',
        run    : 'onRun',
        finish : 'onFinish'
    },
    main: function(target) {
        var events = MyApp.events;
        var _self  = [this];
 
        this.init(target);
 
        $(MyApp).trigger(events.init, _self);
 
        this.run();
 
        $(MyApp).trigger(events.run, _self);
 
        this.finish();
 
        $(MyApp).trigger(events.finish, _self);
    }
});

You will also notice in the main function this line:

$(MyApp).trigger(events.run, _self);

jQuery will then call MyPlugin passing the window’s event object and the reference to MyApp.main contained in the _self variable. As stated earlier, passing a reference to MyApp.main allows my plugin to have access to the properties and methods of main, including its target.

Our demonstration is now complete. There is not space here to enumerate all of the benefits of this architecture but I hope that at least I have managed to demonstrate the possibilities this design creates.

You can download a working copy of this demonstration at myapp.zip or see it in action at jquery.myapp.

Please use the comment form below to let me know what you think and/or to ask any questions.

Comments

3 Responses to “A Pluggable jQuery Extension Architecture”

  1. Dean Landolt on June 26th, 2008 10:29 am

    Damn. I didn’t realize how easy it would be to write pluggability into js apps. If only I could get python to be this accommodating (I *never* thought I’d say that)!

  2. John Ha on August 7th, 2008 4:00 pm

    nice tutorial!

  3. John Ha on August 23rd, 2008 9:59 am

    please include an example of the extension’s plugin in use (toolbar in myapp). atm the toolbar plugin is just sitting there with no reference to it…

Leave a Reply