How jQuery UI uses the widget library


We will create a progress bar. As shown in the example below, this can be done by calling jQuery.widget() with two parameters: the name of the plugin to be created, and an object literal containing the functions that support the plugin. When the plugin is called, it will create a new plugin instance and all functions will be executed in the context of that instance. This differs from the standard jQuery plugin in two important ways. First, context is an object, not a DOM element. Secondly, 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 name of the plug-in must include the namespace. In this example, we used the custom namespace. You can only create namespaces one level deep, so custom.progressbar is a valid plugin name, but 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 an element. If our plugin is called on a jQuery object that contains 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 follows:

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

When we call jQuery.widget(), it passes it by giving jQuery.fn (used to create Standard plugin system) Add functions to extend jQuery. The function name added is based on the name you passed to jQuery.widget() without the namespace - "progressbar". The options passed to the plugin get the values ​​set in the plugin instance. As shown in the example below, we can specify a default value for any of the options. When designing your API, you should be aware of the most common use cases for your plugin so that you can set appropriate defaults and be sure to make all options 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 we can initialize our progress bar, we will perform actions by calling methods on the plugin instance. To define a plugin method, we just reference the function in 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 a method on a plugin instance, you pass the name of the method to the jQuery plugin. If the method you are calling accepts parameters, you simply pass those parameters after the method name.

Note: Execute methods by passing the method name to the same jQuery function used to initialize the plugin. This is done to prevent jQuery namespace pollution while keeping chained method calls. Later in this chapter, we'll see other uses that look 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

option() method is automatically provided to the plugin. option() The method allows you to get and set options after initialization. This method is like jQuery's .css() and .attr() methods: you can pass only a name as a value getter, or you can pass a name and value as a setting Use the handler, or pass a hash of key/key-value pairs to set multiple values. When used as a valuer, 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 set. We can specify a _setOption method in our plugin to react to option changes. For actions that change options to be performed independently, we can overload _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;
    }
});

Add callbacks

The simplest way to extend a plugin is to add callbacks so users can react when the plugin state changes. We can see the following example of how to add a callback to the progress bar when the progress reaches 100%. The _trigger() method takes three parameters: the callback name, a jQuery event object that starts the callback, and a hash of data associated with the event. The callback name is the only required parameter, but the other parameters are 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 the drag callback is fired, which will allow the user to react to dragging based on the x/y coordinates provided by the event object. Please 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;
    }
});

The callback functions are essentially just additional options, so you can get and set them just like other options. Whenever a callback is executed, a corresponding event will be triggered. The event type is determined by the name of the connection plug-in and the callback function name. Both callbacks and events accept the same two parameters: an event object and a hash of data associated with the event, as shown in the example below. Your plugin may need to include functionality that prevents users from using it, and the best way to do this is to create a revocable callback. Users can cancel callbacks or related events, just like they can cancel any native event, by calling event.preventDefault() or returning false. If the user revokes the callback, the _trigger() method will return false, allowing you to implement the 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 see how it actually works. When you call jQuery.widget() it creates a constructor for the plugin and sets the object you pass in as a prototype for the plugin instance. All functionality automatically added to the plugin comes from a base widget prototype, 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.

Since the plugin instance is linked directly to the DOM element, you can access the plugin instance directly without traversing the plugin methods. This will allow you to call methods directly on the plugin instance without passing the method name as a string, and you will also have direct access to 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 and just call the constructor directly with options and elements:

var bar = $.custom.progressbar( {}, $( "<div></div>" ).appendTo( "body") );
 
// Same result as before.
alert( bar.options.value );

Extend the prototype of the plugin

The biggest advantage of plug-ins having constructors and prototypes is that they are easy to extend. By adding or modifying methods on the plugin prototype, we can modify the behavior of all instances of the plugin. For example, if we wanted to add a method to the progress bar that resets the progress to 0%, we could add this method to the prototype and it would 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 completely new widget on top of an existing widget, check out Widget Factory )Extended widget (Widget).

Cleanup

Allow users to apply plugins and then unapply them under certain circumstances. You can do this via the _destroy() method. Within the _destroy() method, you should undo all actions done by the plugin during initialization and later use. _destroy() is called through the .destroy() method, which removes the element bound to the plugin instance from the DOM is called automatically so this can be used for garbage collection. The basic .destroy() method also handles some common cleanup operations, such as removing instance references from the widget's DOM element, unbinding all events in the widget's namespace from the element, and unbinding Define all events added using _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 );
        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( "" );
    }
});
Close comment

The Widget Factory is just a way to create stateful plug-ins. There are a few other different models available, each with their own strengths and weaknesses. The widget factory solves many common problems and greatly improves efficiency. It also greatly improves code reusability, making it suitable for jQuery UI and other stateful plug-ins.

Please note that in this chapter we use the custom namespace. ui The namespace is reserved by the official jQuery UI plugin. When creating your own plugin, you should create your own namespace. This will make it clearer where the plug-in comes from and which scope it belongs to.