Wednesday, November 3, 2010

jQuery : Creating a custom widget


Today, in this post, I am going to discuss how to create a custom widget using jQuery.

Lets begin with the definition of a widget.

A widget in jQuery means nothing but a function that can be called on a jQuery selector that modifies the behaviour of all the selected elements by attaching data to the selected elements and maintaining individual state for each of these elements.

The important point to be noted here is the "state keeping" that is associated with each selected element. This is what makes the difference between a jQuery plugin and a jQuery widget. A widget can be considered to be an advanced form of a plugin.

Widgets can be of several types. For example, you may create a widget that simply formats the data in an element. Or you may create a widget that is closely associated to the structure of a html snippet, because it uses the html structure to create its layout. for e.g the "tabs" widget.

In this post, I am going to create a very basic widget, that actually does nothing useful. My objective here is to write a basic skeleton structure to create a widget that you can replace anytime with your own functionality.

Our prerequisites :

Download the jQuery UI plugin. This plugin contains the javascript files that are necessary to create a widget. Thats because the widget function is available to us via the UI plugin. Of course that does not mean that you can only create UI related widgets using this plugin. You are always only limited by your imagination.
You can download it from here. For the examples in this post, all you need is the four components that fall under the "UI Core" category. We will only be dealing with the basic stuff here. Come back when you are done.

Okey, now that you're back, I want to ensure two things before we begin.
  1. You have extracted the zip file in some arbitrary location on your disc. lets call it "jQueryUI". This folder should contain the "js" folder and the "development-bundle" folder. We shall be using he "jQueryUI" as the root directory where we would be creating our html page in which we would be using jQuery.
  2. You create a page called widgetExperiment.html in your root directory.


In the head section of your html page, include the following lines of code.


  
  
  
  


These are the scripts that we need to refer to in order to create our custom widget. As you can see, the version numbers of your script files may differ based upon when you are actually reading this blog. Just go to the appropriate directories and replace the src file names in the script tag with the proper file names.

Now, we are all set to crate our first jQuery widget.

Lets start with the standard "onload" function in jQuery.

$(function(){
//All our Code goes here
});

When creating a widget, the first thing that we need to do is to create an object that represents the functions and the data of our widget. This object acts as a prototype for our widget data. The purpose of this object is to encapsulate the functions and the properties that will be associated with each selected element returned by the jQuery selector expression.



var myPrototype={
      options:{level:1},
      setLevel:function(level){this.options.level=level;},
      getLevel: function () { return this.options.level; },
      _init: function() { this.setLevel(this.getLevel());}
     };

As can be seen above, I named my prototype object "myPrototype". Inside my prototype, i have a few functions and an object called "options".

There are two things that you need to know about the properties that are created inside a widget.
The function names that begin with an underscore are private. i.e. these functions cannot be called from outside of the widget.

All the data, pertaining to the state of the widget should be kept in an internal object called "options".
An instance of the widget is created for each element returned by the selector. The options object ensures that for each instance of the widget that is created, the properties remain private to that particular instance.

Initialization of your widget if required, must take place in the _init function. You can also create a destroy function to perform any cleanup when your widget instance is destroyed. These functions are called automatically and are part of your widget lifecycle.

As seen in the code, for my widget, i created a private property called "level" and two "getter/setter" functions to manipulate this property. The getter/setter functions are public by virtue of not starting with an underscore.

Calling these functions are not done in the usual manner. We shall see how to invoke these public functions once we have created the widget.

Creating a widget is very simple. All that you need to do is to associate your prototype object with a name and pass it to the jQuery widget function. Here is how it is done

$.widget("ui.myWidget",myPrototype);


The jQuery widget plugin function is used to create your widget by assigning it a name in the namespace "ui.myWidget" and then passing the object that we use as the prototype object in the second parameter.
Although the namespacing is "ui.myWidget", your widget is simply named myWidget and that is what your plugin function will be called.

Associating your widget with an element is a complete no brainer. For example, the following code associates all my divs on the page with my widget. (you dont want to associate all your divs with a widget unless ur a total nut, or if you are testing something like the way I am doing now)

$("div").myWidget();

Now lets see how to use our newly created widget. If you remember, when creating the widget, we declared a property called "level" that set to an initial value of 1 in the widget. Now we are going to write a simple click handler for our divs that sets the value of the level and prints it on the console.

$("div").click(function(){
      
      $(this).myWidget("setLevel",4);
      
      console.log($(this).myWidget("getLevel"));
      
     });



In the following example, notice how we called the public functions getLevel and setLevel. Also notice how we passed a parameter to the setLevel function of our widget. The first argument is treated as the function name and the second argument onwards are treated as the arguments for the function being invoked.

Here is the entire code in a single snippet


var myPrototype={
      options:{level:1},
      setLevel:function(level){this.options.level=level;},
      getLevel: function () { return this.options.level; },
      _init: function() { this.setLevel(this.getLevel());}
     };
     
     
     $.widget("ui.myPrototype",myPrototype);
     
     
     $("div").myPrototype();
     
     $("div").click(function(){
      
      $(this).myPrototype("setLevel",4);
      
      console.log($(this).myPrototype("getLevel"));
      
     });


Well, that seemed pretty easy, didn't it? But our widget lacks one important thing that we would need to do in most of our widgets. That is, to access the current element to which the widget is attached, inside the widget. You cannot use "this", because inside the object, the keyword "this" represents an instance of the widget. The actual element associated with the object is accessed via "this.element", which is a jQuery object representing the dom element.

So, if you were inside the init method and you wanted to print the value of the id of your selected element, that could easily done by the writing the following lines in the init method

console.log("inside init : "+this.element.attr("id"));

So, thats one more thing settled.

Lets add a little more 'dummy' functionality to our code by allowing it to interact with the mouse. In order to allow your widgets to interact with the mouse, you need to create your widgets in a slightly different way.

Here is how you created the widget in the normal case ;

$.widget("ui.myWidget",myPrototype);

And here is how you will create it when you need mouse interaction

$.widget("ui.myWidget",  $.ui.mouse,myPrototype);

Once you have done that, you can need to override a few functions to handle mouse related events on your widget. But before that you should call the _mouseInit and _mouseDestroy methods in the _init and destroy methods of your widget respectively.

Here is the code in all its mighty form!

var myPrototype={
      options:{level:1},
      setLevel:function(level){this.options.level=level;},
      getLevel: function () { return this.options.level; },
      _init: function() { this._mouseInit();},
      
      
      destroy: function() {
       this._mouseDestroy();
       return this;
      },
      
      _mouseCapture: function(event) {
       console.log("inside capture: x("+event.pageX +") y("+event.pageY+")" );
       return true;
      },
      
      _mouseStart: function(event) {
       console.log("inside start: x("+event.pageX +") y("+event.pageY+")" );
       return true;
      },
      
      _mouseDrag: function(event) {
       console.log("inside drag: x("+event.pageX +") y("+event.pageY+")" );
       return false;
      },
      
      _mouseStop: function(event) {
       console.log("inside stop: x("+event.pageX +") y("+event.pageY+")" );
       return false;
      }
      
     };
     
     
     $.widget("ui.myWidget",  $.ui.mouse,myPrototype);

I hope this bare bones example comes in handy to get you started quickly on creating simple widgets.

Thats all for now folks!

Happy Programming :)
Signing Off
Ryan

9 comments:

Anonymous said...

I get an error when calling $("div").myPrototype();

any ideas?

Ryan said...

What is the error you get? You can check out the details of the error the firebug or chrome console. As a prerequisite, have you included all the script tags in the same sequence?

Williams said...

Thank u so much Ryan,

Nice post, Currently I just started to work on jquery widgets, and I found ur post very helpful for me.

Can you please specify some more jQuery widget tutorials links, where I can explore a little bit more.

Please.... :)


Williams.

prakashraj said...

Thank you Ryan, It is helpful

Anonymous said...

Thanks Ryan.

This very helpfull for me.

Anonymous said...

Thanks, it's very clear.

I have a question, how can I do to write some logic on the doubleClick event?

Thanks,
Luciano

Ryan A.S. said...

@Luciano,

The answer to your question depends upon the kind of widget you are creating. Inside the code of your widget, there is no restriction on attaching event handlers to dom elements that are already part of the page or that were introduced by your own code.

Take a look at the documentation for jQuery.on which is now being used instead of the older jQuery.bind function.

If you have a specific issue, maybe you can post some code at jsfiddle.com, share the link so that I can take a look and maybe try to figure out something?

Anonymous said...

Very Helpful. Provide more links for developing Jquery Widgets

손상현 said...

good, clear, coincise, but perspective
thank you