in jQuery

Pluggable jQuery Application

A couple of years ago, while integrating the WYMeditor Semantic HTML editor into SkyBlueCanvas lightweight CMS, I experimented with different ways to simplify adding custom buttons and fuctionality to WYMeditor.

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.

I also wanted other developers to be able to add functionality and not have to worry about collisions between the names of functions and properties used in their plugins and those other plugins. This would require the ability to define custom name spaces.

This article demonstrates a jQuery application architecture that not only satisfies the requirements described above, but also makes even the core features of the application into plugins using the same API.

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

[dm]3[/dm]

Demo

jquery-myapp

Define a New Application Object

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

if (! MyApp) var MyApp = {};

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.

Add the Application Methods

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.

Extend jQuery with MyApp

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 property 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.

Create A Plugin

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)
    });
};

Attach My Plugin to MyApp

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');
};

Bind MyPlugin to an Event

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:

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

The target is the element or object on 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 on the specified target. If you attempt to call the callback on an event that does not exist in the specified target, nothing will happen or, you may get unexpected results if an event by the same name exists but on the wrong target.

Run the Application

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.

Conclusion

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.

Leave a Reply

  1. Hi Scott,

    The download and demo links at the end of this article returned 404s. Is there another url that I should use?

    Thank you

    • Sorry about that. I moved this article from another site I run and didn't update the links. I have done so now and they are both working as they should. Cheers.