Easy Tutorial
❮ Api Jquery Widget Api Removeuniqueid ❯

jQuery UI: How to Use the Widget Factory

We will create a progress bar. As shown in the example below, this can be done by calling jQuery.widget(), which takes two parameters: one is the name of the plugin to be created, and the other is an object literal containing functions that support the plugin. When the plugin is invoked, it will create a new instance of the plugin, and all functions will be executed within the context of that instance. This differs from standard jQuery plugins in two important ways. First, the context is an object, not a DOM element. Second, the context is always a single object, not a collection.

$.widget("custom.progressbar", {
    _create: function() {
        var progress = this.options.value + "%";
        this.element
            .addClass("progressbar")
            .text(progress);
    }
});

The plugin name must include a namespace. In this example, we used the custom namespace. You can only create one level of namespace, so custom.progressbar is a valid plugin name, while very.custom.progressbar is not a valid plugin name.

We see that the Widget Factory provides us with two properties. this.element is a jQuery object containing a single 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 of key-value pairs containing all plugin options. These options can be passed to the plugin as shown:

$("<div></div>")
    .appendTo("body")
    .progressbar({ value: 20 });

When we call jQuery.widget(), it extends jQuery by adding a function to jQuery.fn (the system used for creating standard plugins). The added function is named based on the name you passed to jQuery.widget(), without the namespace - "progressbar". The options passed to the plugin are the values set in the plugin instance. As shown in the example below, we can specify default values for any options. When designing your API, you should clearly define the most common use cases for your plugin so that you can set appropriate default values and ensure that all options are truly optional.

$.widget("custom.progressbar", {
    // Default options.
    options: {
        value: 0
    },
    _create: function() {
        var progress = this.options.value + "%";
        this.element
            .addClass("progressbar")
            .text(progress);
    }
});

Calling Plugin Methods

Now that we can initialize our progress bar, we will perform actions by calling methods on the plugin instance. To define a plugin method, we simply reference a function within the object we pass to jQuery.widget(). We can also define "private" methods by prefixing the function name with an underscore.

$.widget("custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        var progress = 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) {
            return this.options.value;
        }
        // Value passed, act as a setter.
    }
});
this.options.value = this._constrain(value);
var progress = this.options.value + "%";
this.element.text(progress);
},

// Create a private method.
_constrain: function(value) {
    if (value > 100) {
        value = 100;
    }
    if (value < 0) {
        value = 0;
    }
    return value;
}
});

To call methods on a plugin instance, you can pass the method name to the jQuery plugin. If the method you are calling accepts arguments, simply pass those arguments after the method name.

Note: Executing methods by passing the method name to the same jQuery function used to initialize the plugin is done to prevent pollution of the jQuery namespace while maintaining chainability. In later sections of this chapter, we will see other patterns that appear more natural.

var bar = $("<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"));

Using Options

The option() method is automatically provided by the plugin. The option() method allows you to get and set options after initialization. This method works similarly to jQuery's .css() and .attr() methods: you can pass just a name to use it as a getter, or pass a name and value to use it as a setter, or pass 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 corresponding to the name 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. For actions that need to be taken independently for each option change, we can override _setOptions.

$.widget("custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        this.options.value = this._constrain(this.options.value);
        this.element.addClass("progressbar");
        this.refresh();
    },
    _setOption: function(key, value) {
        if (key === "value") {
            value = this._constrain(value);
        }
        this._super(key, value);
    },
    _setOptions: function(options) {
        this._super(options);
        this.refresh();
    },
    refresh: function() {
        var progress = this.options.value + "%";
        this.element.text(progress);
    },
    _constrain: function(value) {
        if (value > 100) {
            value = 100;
        }
        if (value < 0) {
            value = 0;
        }
        return value;
    }
});

Adding Callbacks

The simplest way to extend a plugin is to add callbacks, allowing users to react when the plugin's state changes. We can see in the example below how to add a callback to the progress bar when it reaches 100%. The _trigger() method has three parameters: the callback name, a jQuery event object to initiate the callback, and a data hash related to the event. The callback name is the only required parameter, but the other parameters are very useful for users who want to implement custom functionality on the plugin. For example, if we create a draggable plugin, we can pass the mousemove event when triggering the drag callback, which will allow users to react to the drag based on the x/y coordinates provided by the event object. Note that the original event passed to _trigger() must be a jQuery event, not a native browser event.

$.widget("custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        this.options.value = this._constrain(this.options.value);
        this.element.addClass("progressbar");
        this.refresh();
    },
    _setOption: function(key, value) {
        if (key === "value") {
            value = this._constrain(value);
        }
        this._super(key, value);
    },
    _setOptions: function(options) {
        this._super(options);
        this.refresh();
    },
    refresh: function() {
        var progress = this.options.value + "%";
        this.element.text(progress);
        if (this.options.value == 100) {
            this._trigger("complete", null, { value: 100 });
        }
    },
    _constrain: function(value) {
        if (value > 100) {
            value = 100;
        }
        if (value < 0) {
            value = 0;
        }
        return value;
    }
});

Callbacks are essentially just additional options, so you can get and set them like any other option. Whenever a callback is executed, a corresponding event is triggered. The event type is determined by concatenating the plugin's name and the callback name. Both callbacks and events accept the same two parameters: an event object and a data hash related to the event, as shown in the example below.

Your plugin may need to include functionality to prevent user actions, and the best way to achieve this is by creating a cancelable callback. Users can cancel the callback or the associated event, just as they would cancel any native event, by calling event.preventDefault() or returning false. If the user cancels the callback, the _trigger() method will return false, allowing you to implement appropriate functionality within the plugin.

var bar = $("<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);

Essence

Now that we've seen how to create a plugin using the Widget Factory, let's look at how it actually works. When you call jQuery.widget(), it creates a constructor for the plugin and sets the object you pass in as the prototype for plugin instances. All the functionality that gets added to your plugin automatically comes from a base widget prototype, which is defined as jQuery.Widget.prototype. When a plugin instance is created, it is stored using jQuery.data on the original DOM element, with the plugin name as the key.

Since the plugin instance is directly linked to the DOM element, you can access the plugin instance directly without needing to traverse the plugin methods. This allows you to call methods directly on the plugin instance without passing a string method name, and you can also directly access the plugin's properties.

var bar = $( "<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 );

You can also create an instance without traversing the plugin methods by directly calling the constructor with options and the element:

var bar = $.custom.progressbar( {}, $( "<div></div>" ).appendTo( "body") );

// Same result as before.
alert( bar.options.value );

Extending the Plugin Prototype

The great thing about having a constructor and prototype for a plugin is that it's easy to extend the plugin. By adding or modifying methods on the plugin prototype, we can modify the behavior of all instances of the plugin. For example, if we want to add a method to the progress bar to reset the progress to 0%, we can add this method to the prototype and it will be callable on all plugin instances.

$.custom.progressbar.prototype.reset = function() {
    this._setOption( "value", 0 );
};

For more details on extending widgets and how to create a new widget based on an existing one, see Extending Widgets with the Widget Factory.

Cleanup

In some cases, it's useful to allow users to apply a plugin and then unapply it. You can do this with the _destroy() method. Inside the _destroy() method, you should undo everything that was done in the initial setup and during use of the plugin. _destroy() is called by the .destroy() method, which is automatically called when the element the plugin instance is bound to is removed from the DOM, so this can be used for garbage collection. The base .destroy() method also handles some common cleanup operations, such as removing the instance reference from the widget's DOM element, unbinding all events in the widget namespace from the element, and unbinding all events added with _bind().

$.widget( "custom.progressbar", {
    options: {
        value: 0
    },
    _create: function() {
        this.options.value = this._constrain(this.options.value);
        this.element.addClass( "progressbar" );
        this.refresh();
    },
    _setOption: function( key, value ) {
        if ( key === "value" ) {
            value = this._constrain( value );
        }
        this._super( key, value );
    },
    _setOptions: function( options ) {
        this._super( options );
        this.refresh();
    },
    refresh: function() {
        var progress = this.options.value + "%";
        this.element.text( progress );
    },
    _destroy: function() {
        this.element
            .removeClass( "progressbar" )
            .text( "" );
    }
});
if (this.options.value == 100) {
    this._trigger("complete", null, { value: 100 });
}
},
_constrain: function(value) {
    if (value > 100) {
        value = 100;
    }
    if (value < 0) {
        value = 0;
    }
    return value;
},
_destroy: function() {
    this.element
        .removeClass("progressbar")
        .text("");
}
});

Closing Comments

The Widget Factory is just one way to create stateful plugins. There are a few different models that can be used and each has its own advantages and disadvantages. The Widget Factory solves a lot of common problems and can greatly improve development speed, code reuse, and shareability, making it suitable for jQuery UI and other stateful plugins.

Please note that we have used the custom namespace in this chapter. The ui namespace is reserved for official jQuery UI plugins. When creating your own plugins, you should create your own namespace. This makes it clearer where the plugin comes from and what scope it belongs to. ```

❮ Api Jquery Widget Api Removeuniqueid ❯