Archive

Background

Over the past few years, jQuery has dominated the web development community with its simple, yet brilliant, API. The “find elements, do something” pattern and ability to chain function calls together combine to create code that reads like English. jQuery’s simplicity and almost nonexistent learning curve have made it extremely popular among developers and designers alike. Equally as important as its ease of use is its extensibility. With an extension system that makes creating a plugin as simple as writing a function, jQuery has been able to maintain a lean core while offering an almost unlimited amount of functionality.

The combination of jQuery’s simple plugin system and its active community has resulted in over 2,000 plugins being listed in its plugin repository. However, like all core methods, the majority of these plugins only provide stateless functionality. While many of these plugins are useful, there’s a large set of functionality that doesn’t fit into the basic plugin pattern.

A new plugin system

In order to fill this gap, jQuery UI has implemented a more advanced plugin system. The new system manages state, allows multiple functions to be exposed via a single plugin, and provides various extension points. This system is called the widget factory and is exposed as jQuery.widget. In this article, we’ll explore the various features provided by the widget factory by building a simple progress bar plugin using jQuery UI 1.8.

Building a plugin

To start, we’ll create a progress bar that just lets us set the progress once. As we can see below, this is done by calling jQuery.widget with two parameters: the name of the plugin to create and an object literal containing functions to support our plugin. When our plugin gets called, it will create a new plugin instance and all functions will be executed within the context of that instance. This is different from a standard jQuery plugin in two important ways. First, the context is an object, not a DOM element. Second, the context is always a single object, never a collection.

JavaScript

1

2

3

4

5

6

7

8

$.widget("nmk.progressbar",{

_create:function(){

varprogress=this.options.value+"%";

this.element

.addClass("progressbar")

.text(progress);

}

});

The name of the plugin must contain a namespace, in this case we’ve used the nmk namespace. There is currently a limitation that exactly one namespace must be used. We can also see that the widget factory has provided two properties for us. this.element is a jQuery object containing exactly one element. If our plugin is called on a jQuery object containing multiple elements, a separate plugin instance will be created for each element, and each instance will have its own this.element. The second property, this.options, is a hash containing key/value pairs for all of our plugin’s options. These options can be passed to our plugin as shown here.

JavaScript

1

2

3

$("<div></div>")

.appendTo("body")

.progressbar({value:20});

When we call jQuery.widget it extends jQuery by adding a function to jQuery.fn (the system for creating a standard plugin). The name of the function it adds is based on the name you pass to jQuery.widget, without the namespace; in our case “progressbar”. The options passed to our plugin are the values that get set in this.options inside of our plugin instance. As shown below, we can specify default values for any of our options. When designing your API, you should figure out the most common use case for your plugin so that you can set appropriate default values and make all options truly optional.

1

2

3

4

5

6

7

8

9

10

11

12

$.widget("nmk.progressbar",{

// default options

options:{

value:0

},

_create:function(){

varprogress=this.options.value+"%";

this.element

.addClass("progressbar")

.text(progress);

}

});

Calling plugin methods

Now that we can initialize our progress bar, we’ll add the ability to perform actions by calling methods on our plugin instance. To define a plugin method, we just include the function in the object literal that we pass to jQuery.widget. We can also define “private” methods by prepending an underscore to the function name.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

$.widget("nmk.progressbar",{

options:{

value:0

},

_create:function(){

varprogress=this.options.value+"%";

this.element

.addClass("progressbar")

.text(progress);

},

// create a public method

value:function(value){

// no value passed, act as a getter

if(value===undefined){

returnthis.options.value;

// value passed, act as a setter

}else{

this.options.value=this._constrain(value);

varprogress=this.options.value+"%";

this.element.text(progress);

}

},

// create a private method

_constrain:function(value){

if(value>100){

value=100;

}

if(value<0){

value=0;

}

returnvalue;

}

});

To call a method on a plugin instance, you pass the name of the method to the jQuery plugin. If you are calling a method that accepts parameters, you simply pass those parameters after the method name.

Note: Executing methods by passing the method name to the same jQuery function that was used to initialize the plugin may seem odd. This is done to prevent pollution of the jQuery namespace while maintaining the ability to chain method calls. Later in this article we’ll see alternative uses that may feel more natural.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

varbar=$("<div></div>")

.appendTo("body")

.progressbar({value:20});

// get the current value

alert(bar.progressbar("value"));

// update the value

bar.progressbar("value",50);

// get the current value again

alert(bar.progressbar("value"));

Working with options

One of the methods that is automatically available to our plugin is the option method. The option method allows you to get and set options after initialization. This method works exactly like jQuery’s css and attr methods: you can pass just a name to use it as a setter, a name and value to use it as a single setter, or a hash of name/value pairs to set multiple values. When used as a getter, the plugin will return the current value of the option that corresponds to the name that was passed in. When used as a setter, the plugin’s _setOption method will be called for each option that is being set. We can specify a _setOption method in our plugin to react to option changes.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

$.widget("nmk.progressbar",{

options:{

value:0

},

_create:function(){

this.element.addClass("progressbar");

this._update();

},

_setOption:function(key,value){

this.options[key]=value;

this._update();

},

_update:function(){

varprogress=this.options.value+"%";

this.element.text(progress);

}

});

Adding callbacks

One of the easiest ways to make your plugin extensible is to add callbacks so users can react when the state of your plugin changes. We can see below how to add a callback to our progress bar to signify when the progress has reached 100%. The _trigger method takes three parameters: the name of the callback, a native event object that initiated the callback, and a hash of data relevant to the event. The callback name is the only required parameter, but the others can be very useful for users who want to implement custom functionality on top of your plugin. For example, if we were building a draggable plugin, we could pass the native mousemove event when triggering a drag callback; this would allow users to react to the drag based on the x/y coordinates provided by the event object.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

$.widget("nmk.progressbar",{

options:{

value:0

},

_create:function(){

this.element.addClass("progressbar");

this._update();

},

_setOption:function(key,value){

this.options[key]=value;

this._update();

},

_update:function(){

varprogress=this.options.value+"%";

this.element.text(progress);

if(this.options.value==100){

this._trigger("complete",null,{value:100});

}

}

});

Callback functions are essentially just additional options, so you can get and set them just like any other option. Whenever a callback is executed, a corresponding event is triggered as well. The event type is determined by concatenating the plugin name and the callback name. The callback and event both receive the same two parameters: an event object and a hash of data relevant to the event, as we’ll see below. Your plugin may have functionality that you want to allow the user to prevent. The best way to support this is by creating cancelable callbacks. User’s can cancel a callback, or its associated event, the same way they cancel any native event, by calling event.preventDefault() or returning false. If the user cancels the callback, the _trigger method will return false so you can implement the appropriate functionality within your plugin.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

varbar=$("<div></div>")

.appendTo("body")

.progressbar({

complete:function(event,data){

alert("Callbacks are great!");

}

})

.bind("progressbarcomplete",function(event,data){

alert("Events bubble and support many handlers for extreme flexibility.");

alert("The progress bar value is "+data.value);

});

bar.progressbar("option","value",100);

Looking under the hood

Now that we’ve seen how to build a plugin using the widget factory, let’s take a look at how it actually works. When you call jQuery.widget, it creates a constructor for your plugin and sets the object literal that you pass in as the prototype for your plugin instances. All of the functionality that automatically gets added to your plugin comes from a base widget prototype, which is defined as jQuery.Widget.prototype. When a plugin instance is created, it is stored on the original DOM element using jQuery.data, with the plugin name as the key.
Because the plugin instance is directly linked to the DOM element, you can access the plugin instance directly instead of going through the exposed plugin method if you want. This will allow you to call methods directly on the plugin instance instead of passing method names as strings and will also give you direct access to the plugin’s properties.

JavaScript

1

2

3

4

5

6

7

8

9

10

varbar=$("<div></div>")

.appendTo("body")

.progressbar()

.data("progressbar");

// call a method directly on the plugin instance

bar.option("value",50);

// access properties on the plugin instance

alert(bar.options.value);

Extending a plugin’s prototype

One of the biggest benefits of having a constructor and prototype for a plugin is the ease of extending the plugin. By adding or modifying methods on the plugin’s prototype, we can modify the behavior of all instances of our plugin. For example, if we wanted to add a method to our progress bar to reset the progress to 0% we could add this method to the prototype and it would instantly be available to be called on any plugin instance.

JavaScript

1

2

3

$.nmk.progressbar.prototype.reset=function(){

this._setOption("value",0);

};

Cleaning up

In some cases, it will make sense to allow users to apply and then later unapply your plugin. You can accomplish this via the destroy method. Within the destroy method, you should undo anything your plugin may have done during initialization or later use. The destroy method is automatically called if the element that your plugin instance is tied to is removed from the DOM, so this can be used for garbage collection as well. The default destroy method removes the link between the DOM element and the plugin instance, so it’s important to call the base function from your plugin’s destroy method.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

$.widget("nmk.progressbar",{

options:{

value:0

},

_create:function(){

this.element.addClass("progressbar");

this._update();

},

_setOption:function(key,value){

this.options[key]=value;

this._update();

},

_update:function(){

varprogress=this.options.value+"%";

this.element.text(progress);

if(this.options.value==100){

this._trigger("complete",null,{value:100});

}

},

destroy:function(){

this.element

.removeClass("progressbar")

.text("");

// call the base destroy function

$.Widget.prototype.destroy.call(this);

}

});

Closing comments

The widget factory is only one way of creating stateful plugins. There are a few different models that can be used and each have their own advantages and disadvantages. The widget factory solves lots of common problems for you and can greatly improve productivity, it also greatly improves code reuse, making it a great fit for jQuery UI as well as many other stateful plugins.

You may have noticed that in this article we used the nmk namespace. The ui namespace is reserved for official jQuery UI plugins. When building your own plugins, you should create your own namespace. This makes it clear where the plugin came from and if it is part of a larger collection.

In a previous article, I explained how to load a page into a dialog. The article focused on a simple solution to keep it easy to understand. However, that simplicity does come with a drawback; the page contents are loaded immediately after the document is ready, even if the user never opens the dialog. In this article, I’ll show how to load the page on demand while still only making a single request regardless of how many times the user opens the dialog.

On-demand page loading

In order to create the dialog on the first click, we’ll take advantage of jQuery’s .one() event binding method. .one() binds a handler to an event, but the handler is only ever run once. Immediately after the handler runs it unbinds itself, preventing further execution. We’ll adapt the example from the previous article to take advantage of .one():

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

$(document).ready(function(){

$('#page-help').each(function(){

var$dialog=$('<div></div>');

var$link=$(this).one('click',function(){

$dialog

.load($link.attr('href'))

.dialog({

title:$link.attr('title'),

width:500,

height:300

});

$link.click(function(){

$dialog.dialog('open');

returnfalse;

});

returnfalse;

});

});

});

Adding the final touch

Now that we’ve got our dialogs loading on demand, we need to account for network latency, slow connections, etc. Previously we were loading the page contents before the user ever saw the dialog, so any delay was hidden. A simple solution for this is to just show a loading image inside the dialog until the page contents are loaded. We’ll load this image immediately on document ready to make sure that it’s ready for use when the user clicks the link to open the dialog. Loading images are generally very small in terms of file size and they can be cached by the user, so the overhead of loading the image up-front is negligible.

I previously showed the basic usage of the jQuery UI dialog. In this article, I’ll show how to open a page in a dialog. We’ve all been to sites where there’s a help link that opens a popup with some short help text. This is usually done with a simple window.open call attached to the click event of the link.

Making it better

The one thing that this code has going for it is that users without JavaScript will still be able to get to the page with the help content. Of course, a better implementation would move the JavaScript out of the HTML, properly separating content from behavior. We can spruce this up by loading the content into a dialog instead of a new window:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

$(document).ready(function(){

$('#page-help').each(function(){

var$link=$(this);

var$dialog=$('<div></div>')

.load($link.attr('href'))

.dialog({

autoOpen:false,

title:$link.attr('title'),

width:500,

height:300

});

$link.click(function(){

$dialog.dialog('open');

returnfalse;

});

});

});

How it works

We’re finding the link, loading the contents of the linked page into a div and creating a dialog from that div. We then bind a click event to the link to show the dialog. This code by itself will work in many situations. However, the page you’re linking to may have a heading that you’re already reproducing with the dialog title. There may also be other elements on the page that won’t make sense in a popup, such as navigational elements or a page footer. Luckily the .load() function allows us to pass in a selector to find the contents that we care about. In this example we’ll assume the main content of the page is in a div with an id of content.

The jQuery UI dialog, like many jQuery UI plugins, is extremely easy to get started with but has a few areas that causes new users some trouble. One of the most commonly asked questions on the jquery-ui list is “Why does my dialog only open once?” In this article I’ll explain the problem these users are running into and how to get your dialogs to show each and every time.

Problem:

All jQuery UI plugins maintain state, such as the current option values, whether the plugin is enabled or disabled, which plugins have been initialized on the element, etc. This state persists from the time the plugin is instantiated on the element until it is destroyed, either explicitly by the user calling .pluginName('destroy') or by removing the element (or one of its ancestors) via .remove(). Because of this state management, you cannot instantiate the same plugin on an element multiple times, unless you destroy the plugin instance first.

The problem that users often encounter with dialogs is that they try to instantiate a new dialog every time the user performs some action (generally clicking a link or a button). This is an understandable mistake because at first glance it seems like calling .dialog() on an element is what causes the dialog to open. In reality what is happening is that a new dialog instance is being created and then that instance is being opened immediately after instantiation. The reason that the dialog opens is because dialogs have an autoOpen option, which defaults to true. So when a user calls .dialog() on an element twice, the second call is ignored because the dialog has already been instantiated on that element.

Solution:

The simple solution to this problem is to instantiate the dialog with autoOpen set to false and then call .dialog('open') in the event handler.