• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Widget factory

This version was saved 13 years ago View current version     Page history
Saved by ajpiano
on April 4, 2011 at 6:11:32 pm
 

All of jQuery UI's widgets and interactions are built from a simple, reusable base - the jQuery UI Widget Factory. It provides a flexible base for building complex, stateful plugins with a consistent API. It is designed not only for plugins that are part of jQuery UI, but for general consumption by developers who want to create object-oriented components without reinventing common infrastructure.  It does not have any dependencies on the rest of jQuery UI, but most of jQuery UI is dependent on it.

 

What is it?

 

The widget factory is simply function on the global jQuery object - jQuery.widget - that accepts 2 or 3 arguments.

 

jQuery.widget("namespace.widgetname", /* optional - an existing widget prototype to inherit from */, /* An object literal to become the widget's prototype*/ {...} ); 

 

The first argument to the widget factory is a string with a namespace and the widget name, delineated by a dot.  The namespace is mandatory, and it refers to the location on the global jQuery object where the widget prototype will be stored.  If the namespace does not exist, the widget factory will create it for you. The name serves as the actual name of the plugin function and prototype.  For instance, jQuery.widget( "squee.chair, {...})will create jQuery.squee and jQuery.squee.chair.prototype.  

 

The second (optional) argument is a widget prototype to inherit from. For instance, jQuery UI has a "mouse" plugin on which the rest of the interaction plugins are based. In order to achieve this, draggable, droppable, etc. all inherit from the mouse plugin like so: jQuery.widget( "ui.draggable", $.ui.mouse.prototype, {...}); If you do not supply this argument, the widget will inherit directly from the "base widget," jQuery.Widget

 

The last argument to the widget factory is an object literal that will be transformed into the prototype for each instance of the widget.  The widget factory sets up the prototype chain, connecting the widget's prototype to any widgets from which it inherits, up to the base jQuery.Widget.

 

Once you make the call to jQuery.widget,  there will be a new method available on the jQuery prototype (jQuery.fn) that corresponds to the widget name - in the case of our example, jQuery.fn.chair. This .fn method serves as the interface between the DOM elements contained in a given jQuery object and instances of the widget prototype you created.  A new instance of the widget is created for each element in the jQuery object 

 

Advantages

 

The basic approach described in the Plugin Authoring Guidelines leaves a lot up to the implementor when it comes to implementing stateful, object-oriented plugins. Furthermore, it doesn't offer any conveniences to obscure away common plumbing tasks. The Widget Factory provides you with jQuery UI's API for allowing communication with your plugin instance, and obfuscates a number of repetitive tasks.

 

  • Creation of a namespace (if necessary) and prototype
    • A pseudoselector is also generated from the namespace and name, for querying and filtering - $(":squee-chair")  
  • Linkage between prototype and jQuery.fn

    • This is accomplished using jQuery.widget.bridge

  • Merging "default" options with user-supplied overrides
    • It does this automatically:

 

$.fn.chair = function(options) { 
     options = $.extend( {}, $.fn.chair.defaults, options);
};

$.fn.chair.defaults = {
     items: "div",
     sat: $.noop 
};

$("#something").chair({
     sat: function() {
          alert("It Finished."); 
     }
}); 

 

 

  • Plugin instance accessible via $("#something").data("pluginname")
    • A reference to a jQuery object containing the DOM element is available as a property of the instance as this.element, so it is easy to go back and forth between the object and the element.

  • The widget's methods are accessible via string invocation through the method on the jQuery prototype - $("#something").chair("sit") - or directly from the instance itself - $("#something").data("chair").sit()

  • Prevention against multiple instantiations of the widget on the same element 

  • A mechanism for dispatching callbacks the plugin user can subscribe to: this._trigger("sat")

    • The user can subscribe via an option:

      • $("#something").chair({ sat: function() { }); 

    • Or using .bind()

      • $("#something").bind("chairsat", function( e ) { }); 

  • A mechanism for facilitating and responding to changes to plugin options after instantiation
    • $("#something").chair("option", "sat" , function ( e ) { alert("Who's Sitting Now!"); }); 
  • Easily enable or disable the widget, or destroy the instance entirely and return the element to its original state

 

Building Your Prototype

 

The object literal you provide to the Widget Factory to serve as the widget prototype can be as complicated as you need, but at a minimum, it should contain default options, and basic _create, _setOption, and destroy callbacks

 

function($) {
  $.widget("squee.chair", {
    // These options will be used as defaults
    options: { 
     items : "div",
     sat: $.noop 
    },
    // Set up the widget
     _create: function() {
    },
    // Use the _setOption method to respond to changes to options
    _setOption: function( key, value ) {
     switch( key ) {
        case "items":
          break;
      }
     // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget
     $.Widget.prototype._setOption.apply(this,arguments) 
     // In jQuery UI 1.9 and above, you use the _super method instead
     this._super( "_setOption", key, value )
    },
    // Use the destroy method to clean up any modifications your widget has made to the DOM 
    destroy: function() {
 
     // In jQuery UI 1.8, you must use invoke the destroy method from the base widget
     $.Widget.prototype.destroy.call(this);
     // In jQuery UI 1.9 and above, this step is not necessary
    }
  });
})(jQuery);

 

 

Properties:

 

this.element

The element that was used to instantiate the plugin.  For example, if you were to do $('#foo').myWidget(), inside your widget instance, this.element would be a jQuery object containing the element with id foo. If you select multiple elements and call .myWidget() on the collection, a separate plugin instance will be instantiated for each element.  In other words, this.element will always contain exactly one element.

 

this.options

The options currently being used for the plugin configuration.  On instantiation, any options provided by the user will automatically be merged with any default values defined in $.ui.myWidget.defaults.  If the metadata plugin is available, any metadata stored on the element in the namespace of the plugin (myWidget) will also be merged into the options on instantiation.  User specified options override metadata, which override the defaults.

 

this.namespace

The namespace the plugin lives in, in this case "ui".  This is usually not needed inside of individual plugins.

 

this.name

The name of the plugin, in this case "myWidget".  Slightly more useful than this.namespace, but generally not needed inside of individual plugins.

 

this.widgetEventPrefix

This is used to determine how to name events that are associated with any callbacks the plugin provides.  For example, dialog has a close callback, and when the close callback is executed, a dialogclose event is triggered.  The name of the event is the event prefix + the callback name.  The widgetEventPrefix defaults to the widget name, but can be overridden if the event names should be different.  For example, when a user starts dragging an element, we don't want the name of the event to be draggablestart, we want it to be dragstart, so we override the event prefix to be "drag".  In order to override the widgetEventPrefix, you would set $.ui.myWidget.eventPrefix to whatever you want the new prefix to be.  If the name of the callback is the same as the event prefix, then the event name will not be prefixed.

 

this.widgetBaseClass

This is useful for building class names to use on elements within your widget.  For example, if you wanted to mark an element as being active, you could do element.addClass(this.widgetBaseClass + '-active').  This isn't really necessary to use in individual plugins because you can just as easily do .addClass('ui-myWidget-active').  This is more for use inside the widget factory and abstract plugins like $.ui.mouse.

 

Methods:

 

_create

This is where you setup everything related to your widget, such as creating elements, binding events, etc.  This gets run once, immediately after instantiation.

 

_init

This method is invoked anytime your widget is invoked without arguments, allowing for [need better words here]

 

destroy

This destroys an instantiated plugin and does any necessary cleanup.  All modifications your plugin performs must be removed on destroy.  This includes removing classes, unbinding events, destroying created elements, etc.  The widget factory provides a starting point, but should be extended to meet the needs of the individual plugin.

 

option

Used for getting and setting options after instantiation.  The method signature is the same as .css() and .attr().  You can specify just a name to get a value, a name and value to set a value, or a hash to set multiple values.  This method calls _setOptions internally, so this method should never need to be modified by an individual plugin.

 

_setOptions

An internal utility method that is used for setting options after instantiation.  This method calls _setOption internally, so this method should never need to be modified by an individual plugin.

 

_setOption

Called when a user sets an option value via the option method.  This method may need to be modified by an individual plugin so the plugin can react when certain options change.  For example, when a dialog's title option changes, the text inside the title bar must be updated.

 

_setOption: function(key, value) {
    if (key == 'title') {
        this.titleElement.text(value);
    }
    $.widget.prototype._setOption.apply(this, arguments);
}

 

enable

Helper method that just calls option('disabled', false).

 

disable

Helper method that just calls option('disabled', true).

 

_trigger

This method must be used to execute all callbacks.  The only required parameter is the name of the callback to execute.  All callbacks also trigger events (see notes about this.widgetEventPrefix above).  You may also provide an event object that represents the event that initiated the process.  For example, a drag event is initiated by a mousemove event, so the original mousemove event object must be passed in to _trigger.  The third parameter is a hash of data that will be passed as a parameter to the callback and event handlers.  Data provided in this hash should only be information that is relevant to the specific event and is not readily available thorugh some other use of the plugin API.

 

Other Benefits and Use:

 

The widget factory will also protect against trying to instantiate a plugin multiple times on the same element.  For example, you can't make an element draggable twice unless you destroy it between instantiations.  It also prevents access to methods that are prefixed with an underscore.

 

Plugins using the widget factory only deal with the plugin instance and never with the jQuery object the method is being called on.  When a method is called on your plugin from a jQuery object, the widget factory will delegate the method call to the appropriate plugin instances.  The widget factory will also handle chaining for you automatically.  If the method returns the plugin instance, then the widget factory will make the call chainable and invoke the call on each plugin instance; if the method returns a value other than the plugin instance, that value will be returned from the original call on the jQuery object. (this is planned for 1.8, see below for 1.7)

 

Whenever an individual plugin overrides a method defined on $.widget.prototype, the original method should be called:

 

$.widget('ui.myPlugin', {
    destroy: function() {
        this.element.removeAttr('something');
        $.widget.prototype.destroy.apply(this, arguments);
    }
});

 

To be changed in 1.8:

Since $.widget delegates all method calls to your plugin and handles chaining for you, you need to specify which methods should return a value instead of being chainable.  You would do this by specifying a space separated list of methods that return values in $.ui.myWidget.getter.  In jQuery UI 1.8 this will change so that you don't need to specify which methods return values anymore.  At that point, all methods will either return the plugin instance or undefined (no return value) to be chainable, or any other value to act as a getter.

Comments (0)

You don't have permission to comment on this page.