Monday, October 27, 2008

Extending jQuery the Object Oriented Way

Though I have used quite a few Ajax libraries in the last couple of years, nothing has impressed me like jQuery. However jQuery is not object oriented in the traditional sence. You cannot instantiate a jQuery object like this
var myobject = new jQuery();

or extend jQuery like this.
extend(MyClass, jQuery);

So what if you could object orientify jQuery? Imagine all your classes/widgets as extensions of jQuery. You could call all the jQuery functions from within your own 'this' like
this.addClass('classname');
this.click(function() {alert('you clicked me')});

But then you cannot extend jQuery because it does not have a constructor! It is itself an object. But thats where javascript comes to your rescue. Javascript does not differentiate an object from a class. And the jQuery object has some features that will come as a help. It has a prototype object and though it does not have a constructor it does have an init method. Some of you are already figuring where I am headed. But before we go ahead we need a function that will do a proper object oriented extend. And I will also add a namespace and call it "JX" for jQuery Extend. Here is the code.
var JX = {
    extend: function(bc, sc, o) {
        var f = function() {};
        f.prototype = sc.prototype;
        bc.prototype = new f();
        bc.prototype.constructor = bc;
        bc.superclass = sc.prototype;
        for (var m in o)
            bc.prototype[m] = o[m];
    }
};

JX.extend() is a function that will take in three parameters, a baseclass constructor, a superclass constructor and an object of functions to override any superclass methods. Now let us use this method to create a new Class called JX.Component that will be a base class for all our widgets.
JX.Component = function() {
JX.Component.superclass.init.apply(this, arguments);
};
JX.extend(JX.Component, jQuery, {});

The arguments passed can be any argument you pass into the jQuery $() function. Voila! You have a class that extends jQuery!
Now try this.

$(document).ready(function() {
    var mydiv = new JX.Component(document.createElement('div'));
    mydiv.text("Hello World").click(function(){alert("You Clicked Me!")});
    mydiv.appendTo(document.body);
);

Now let us get down to something more usefull. Let us extend JX.Component to make a button class.

JX.Button = function() {
JX.Button.superclass.constructor.apply(this, arguments);
this.initialize();
};
JX.extend(JX.Button, JX.Component, {
    initialize: function() {
        var component = this;
        this.hover(
            function() {
                component.css({cursor: 'pointer', opacity: '0.5'})
            },
            function() {
                component.css({cursor: 'default', opacity: '1'})
           }
        );
        this.click(function() {
            alert("You clicked a button with text: " +component.text());
        });
    }
});

Now try your button class with this code.

$(document).ready(function() {
    var mybutton = new JX.Button(document.createElement('div'))
        .text("Click Me")
        .css({background: 'darkblue', color: 'lightblue', textAlign: 'center', width: '100px'})
        .appendTo(document.body);
});    

If you noticed the Button constructor called its "superclass.constructor". However JX.Component when it extends jQuery calls its "superclass.init". And thats the trick in extending jQuery.

16 comments:

  1. I'd write that Button as a plugin:

    $.fn.button = function() {
    return this.hover(...).click(...);
    });

    $(function() {
    $("div").button().text("click me");
    });

    ReplyDelete
  2. Here i have just shown an example. However if you are developing a large application, and you want to be 'properly' object oriented then you might want to look at it the way I have shown.

    ReplyDelete
  3. I'm not buying that argument. jQuery's plugin architecture works very well to build large application, especially when combined with event-based programming. You don't need OO to build "complex" applications.

    ReplyDelete
  4. As I suggested earlier its a matter of choice.

    ReplyDelete
  5. The jQuery "object" is not really an object, but a function.
    It used to be jQuery's constructor but then jQuery.fn.init took its place.

    One could probably use simple inheritance (or an OO framework) to extend jQuery by using jQuery.fn.init as constructor.

    You can indeed extend jQuery itself and have the 'this' inside all its function, by adding methods to its prototype (like with any other function/class).

    In short, the only difference between jQuery and a regular js 'class' is that the contructor is hidden withing its init() method.

    ReplyDelete
  6. Your content is not as big a concern as your sensationalist blog title.

    While I'm sure you want to drive traffic to your blog, using a title that somehow asserts that John (or anyone on the jQuery team) is keeping something from the jQuery community is a bit irresponsible.

    You offered interesting content but the title was really less than stellar.

    Rey
    jQuery Team

    ReplyDelete
  7. To ans Rey I must say that i had not expected a reaction like this, I had not taken this so seriously, however I understand where you are coming from, and have changed the title immediately.

    ReplyDelete
  8. Thank you Santosh. Also, let me extend my apologies as I believe I came across too strong and overly sensitive. That's not what the jQuery project is about and we genuinely try to be objective. I think today was my day to have a bad moment.

    My apologies for being a bit curt.

    Rey
    jQuery Team

    ReplyDelete
  9. Santosh, thanks for the great article.

    Do you have any idea how I would then go about extending an object with custom methods so that the following pseudo code could happen.

    //note myCustomMethod

    var mybutton = new JX.Button(document.createElement('div'))
    .text("Click Me")
    .css({background: 'darkblue', color: 'lightblue', textAlign: 'center', width: '100px'})
    .appendTo(document.body)
    .myCustomMethod();

    ReplyDelete
  10. First of all thanks for the article Santosh, it seems to have properly ended the day for us today.

    As for the less pleasant comments the jQuery representative has left you, I'd like to remind them, that jQuery tries to sort of reinvent the wheel when it comes to object orientation, and while they, as experts in their own field, may appreciate, other people that may be still fond of classical oop, will definently get to appreciate your article, including the title.

    I don't think jQuery is taking a very ... open ... aproach to all of this. It seems to be a lot like php, in the sense that, it turns smart developers away from learning oop, which is VERY useful, regardless if you want to spend the time learning the jquery alternative or not ...

    ReplyDelete
  11. @kosz thanks for your comments.
    You might be interested in looking at the library I developed based on this concept.
    http://jx.myofiz.com

    ReplyDelete
  12. oh that's nice, very much like extjs, but completely on top of jQuery. this is indeed similar to what i'm trying to achieve only that I'm just looking for a set of focused components, on top of jquery, that do only what we need them to do, and are based on proper object oriented concepts.

    May I ask, do you have any plans for client side templating, similar to the broad functionality that XTemplate provides ( if you know it from ext ) ?

    ReplyDelete
  13. Sorry for the delay in answering. No I dont have plans for client side templates.

    ReplyDelete
  14. Would you explain more about (this,arguments) at: JX.Component.superclass.init.apply(this, arguments);

    I cannot understand what is "this" refer to, and what is "arguments"

    Thank you very much.

    ReplyDelete
  15. Can you suggest me how to add attributes for a class.

    Thank you very much.

    ReplyDelete
  16. @bluecell

    The apply() method is described very well on Mozilla's MDN.

    https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/function/apply

    ReplyDelete