All of jQuery UI's widgets andinteractions are built from a simple, reusable base - the jQuery UI WidgetFactory. It provides a flexible/灵活/ base for building complex,stateful plugins with a consistent API. It is designed not only for plugins that are part ofjQuery UI, but for general consumption/使用、消费/ by developers who want to create object-oriented components withoutreinventing/重新写/ common infrastructure/基础结构/. It does not have any dependencies on the rest of jQuery UI, but most ofjQuery UI is dependent on it.
What is it?
The widget factory is a simplefunction on the global jQuery object -jQuery.widget -that accepts 2 or 3 arguments.
jQuery.widget("namespace.widgetname", /* optional - an existing widget prototype toinherit from */, /* An object literal to become the widget's prototype*/ {...});
The first argument to the widgetfactory is a string containing anamespace and thewidget name, separated by a dot. The namespaceis mandatory, and it refers to the location on the global jQuery object wherethe widget prototype will be stored. If the namespace does not exist, thewidget factory will create it for you. The widget name serves as the actualname of the plugin function and prototype. For instance,jQuery.widget( "demo.multi",{...} )will createjQuery.demo,jQuery.demo.multi, andjQuery.demo.multi.prototype.
The second (optional) argument is awidget prototype to inherit from. For instance, jQuery UI has a"mouse" plugin on which the rest of the interaction plugins arebased. In order to achieve this, draggable, droppable, etc. all inherit fromthe mouse plugin like so: jQuery.widget( "ui.draggable", $.ui.mouse, {...} );If you do not supply this argument,the widget will inherit directly from the "base widget,"jQuery.Widget (note the difference between lowercase"w"jQuery.widget and uppercase "W"jQuery.Widget).
The last argument to the widgetfactory is an object literal that will be transformed into the prototype foreach 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 tothe base jQuery.Widget.
Once you make the call to jQuery.widget, there will be a new method available onthe jQuery prototype (jQuery.fn) that corresponds to the widgetname - in the case of our example, jQuery.fn.multi. This .fn method serves as the interface between the DOMelements contained in a given jQuery object and instances of the widgetprototype you created. A new instance of the widget is created for each elementin the jQuery object.
Advantages
The basic approach described in the Plugin Authoring Guidelines leaves a lot up to the implementorwhen it comes to implementing stateful, object-oriented plugins. Furthermore,it doesn't offer any conveniences to obscure away common plumbing tasks. Thewidget factory provides you with jQuery UI's API for allowing communicationwith your plugin instance, and abstracts a number of repetitive tasks.
Building Your Prototype
Infrastructure
The object literal you provide tothe widget factory to serve as the widget prototype can be as complicated asyou need, but at a minimum, it should contain defaultoptions, and basic_create,_setOption, anddestroy callbacks
(function( $ ) {
$.widget( "demo.multi", {
// These options will be used as defaults
options: {
clear: null
},
// Set up the widget
_create: function() {
},
// Use the _setOption method to respond tochanges to options
_setOption: function( key, value ) {
switch( key ) {
case "clear":
// handle changes to clear option
break;
}
// In jQuery UI 1.8, you have to manuallyinvoke the _setOption method from the base widget
$.Widget.prototype._setOption.apply(this, arguments );
// In jQuery UI 1.9 and above, you usethe _super method instead
this._super( "_setOption", key,value );
},
// Use the destroy method to clean up anymodifications your widget has made to the DOM
destroy: function() {
// In jQuery UI 1.8, you must invoke thedestroy method from the base widget
$.Widget.prototype.destroy.call( this );
// In jQuery UI 1.9 and above, you woulddefine _destroy instead of destroy and not call the base method
}
});
}( jQuery ) );
Encapsulation into Methods
This object will also likely need tocontain methods to handle various bits of your widget-specific functionality,like building and appending new elements or handling events. It is wise to usediscrete methods to handle each chunk of functionality, instead of handling toomuch directly in your _create method. This will enable you toreact to eventual changes in state without duplicating code.
For instance, in a hypotheticalwidget to enhance a<selectmultiple>,one mighthave to iterate over the<option>s in the select to create acorresponding<li> in a proxy <ul>.This could be accomplished in the_create method, like so:
_create: function() {
var self = this;
this.list = $( "<ul>").insertAfter( this.element );
this.element.hide().find("option" ).each(function( i, el ) {
var $el = $( el ),
text = $( el ).text(),
item = $( "<liclass='multi-option-item'>" + text + "</li>" );
item.appendTo( self.list ).click(function(){
console.log( $el.val() );
});
});
}
Unfortunately, leaving this code in _create makes it difficult to manage the relationship betweenthe original<option>elements andthe list items, or deal with the problem of reflecting the state of<option> elements that have been added to orremoved from the original<select> after thewidget is instantiated. Instead, we build a refresh method, responsibleexclusively for dealing with this linkage, and invoke it from_create. We'll also keep the logic for handling clicks on thelist items separate, and we'll use event delegation to avoid having to bind newhandlers when new list items are created.
_create: function() {
this.list = $( "<ul>" )
.insertAfter( this.element )
.delegate( "li.multi-option-item","click", $.proxy( this._itemClick, this ) );
this.element.hide();
this.refresh();
},
refresh: function() {
// Keep track of the generated list items
this.items = this.items || $();
// Use a class to avoid working on optionsthat have already been created
this.element.find("option:not(.demo-multi-option)" ).each( $.proxy(function( i, el ) {
// Add the class so this option willnot be processed next time the list is refreshed
var $el = $( el ).addClass("demo-multi-option" ),
text = $el.text(),
// Create the list item
item = $( "<liclass='multi-option-item'>" + text + "</li>" )
.data("option.multi", el )
.appendTo( this.list );
// Save it into the item cache
this.items = this.items.add( item );
},this));
// If the the option associated with thislist item is no longer contained by the
// real select element, remove it from thelist and the cache
this.items = this.items.filter($.proxy(function( i, item ) {
var isInOriginal = $.contains(this.element[0], $.data( item, "option.multi" ) );
if ( !isInOriginal ) {
$( item ).remove();
}
return isInOriginal;
}, this ));
},
_itemClick: function( event ){
console.log( $( event.target ).val() );
}
Private vs. Public Methods
As you may have noticed, some of themethods on the prototype are prefixed with an underscore, and others are not.Methods prefixed with an underscore are considered to be "private" byjQuery UI. The widget factory will block any calls made via $.fn to a private method:$( "#something" ).multi( "_create" ) will throw an exception. Sincethese private methods exist directly on the widget prototype, however, they areprivateby convention only.When a reference to the actual widgetinstance is acquired via.data(),any of its methods can be invokeddirectly:$("#something" ).data( "multi" )._create().
So how do you know which is theappropriate decision? If your widget's users will likely have a need for aparticular method's functionality, make it public. The refresh example is acase in point: Since the user will be the one manipulating elements in theoriginal <select>,we mustprovide the facility for him to update the proxy. On the other hand, a plumbingfunction to handle input on the proxy elements the widget creates, like the _itemClick method, is only for internal use, so wemake it private and block it from the widget's public API.
Properties:
this.element
The element that was used toinstantiate the plugin. For example, if you were to do$( "#foo" ).myWidget(), then inside your widget instancethis.element would be a jQuery object containing theelement with id foo. If you select multiple elements and call.myWidget() on the collection, a separate plugininstance will be instantiated for each element. In other words,this.element will always contain exactly one element.
this.options
The options currently being used forthe plugin configuration. On instantiation, any options provided by the userwill automatically be merged with any default values defined in$.demo.multi.prototype.options. User specified options overridethe defaults.
this.namespace
The namespace the plugin lives in,in this case "demo". This is usually not needed inside of individualplugins.
this.name
The name of the plugin, in this case"multi". Slightly more useful thanthis.namespace, but generally not needed inside of individual plugins.
this.widgetEventPrefix
This is used to determine how toname events that are associated with any callbacks the plugin provides. Forexample, 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 canbe overridden if the event names should be different. For example, when a userstarts dragging an element, we don't want the name of the event to bedraggablestart, we want it to be dragstart, so we override the event prefix tobe "drag". If the name of the callback is the same as the eventprefix, then the event name will not be prefixed. This prevents an event namelike dragdrag.
this.widgetBaseClass
This is useful for building classnames to use on elements within your widget. For example, if you wanted to markan element as being active, you could doelement.addClass( this.widgetBaseClass +"-active" ). This isn'treally necessary to use in individual plugins because you can just as easily do.addClass( "demo-multi-active"). This ismore for use inside the widget factory and abstract plugins like $.ui.mouse.
Methods:
_create
This is where you setup everythingrelated to your widget, such as creating elements, binding events, etc. Thisgets run once, immediately after instantiation.
_init
This method is invoked anytime yourwidget is invoked without either 0 arguments or with a single options argument.This could be the first time it is invoked, in which case _init will get calledafter _create. It could also be called at any time after the widget create, inwhich case _init allows you to handle any re-initialization without forcing theuser to perform a destroy->create cycle.
destroy
This destroys an instantiated pluginand does any necessary cleanup. All modifications your plugin performs must beremoved 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 optionsafter 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, ora hash to set multiple values. This method calls _setOptions internally, sothis method should never need to be modified by an individual plugin.
_setOptions
An internal utility method that isused for setting options after instantiation. This method calls _setOptioninternally, so this method should never need to be modified by an individualplugin.
_setOption
Called when a user sets an optionvalue via the option method. This method may need to be modified by anindividual plugin so the plugin can react when certain options change. Forexample, when a dialog's title option changes, the text inside the title barmust be updated.
_setOption: function(key,value) {
if (key === 'title') {
this.titleElement.text(value);
}
$.Widget.prototype._setOption.apply(this,arguments);
}
By calling the base _setOption, weget the default side effect of setting the option to the new value. This shouldnot be performed by _setOption. In some instances, it's necessary to comparethe old and new values to determine the correct side effects. In those instance,you can compare this.options[key] with value as long as you delay the call tothe base _setOption until the end. If you don't need to compare the old and newvalues, you can call the base _setOption at top of your _setOption function.
enable
Helper method that just callsoption('disabled', false). Note that you'll want to handle this by having anif (key === "disabled") block in your _setOption
disable
Helper method that just callsoption('disabled', true). Note that you'll want to handle this by having anif (key === "disabled") block in your _setOption
_trigger
This method must be used to executeall callbacks. The only required parameter is the name of the callback toexecute. All callbacks also trigger events (see notes aboutthis.widgetEventPrefix above). You may also provide an event object thatrepresents the event that initiated the process. For example, a drag event isinitiated by a mousemove event, so the original mousemove event object must bepassed in to _trigger. The third parameter is a hash of data that will bepassed as a parameter to the callback and event handlers. Data provided in thishash should only be information that is relevant to the specific event and isnot readily available thorugh some other use of the plugin API.
Other Benefits and Use:
Plugins using the widget factoryonly deal with the plugin instance and never with the jQuery object the methodis being called on. When a method is called on your plugin from a jQueryobject, the widget factory will delegate the method call to the appropriateplugin instances. The widget factory will also handle chaining for youautomatically. If the method returns the plugin instance, then the widgetfactory will make the call chainable and invoke the call on each plugininstance; if the method returns a value other than the plugin instance, thatvalue will be returned from the original call on the jQuery object.