jquery ui widget提供了对jquery ui的widget进行创建和使用的基本运行框架和接口规范。其主要作用是对定义的widget组件提供组件注册和继承机制的功能。
在jquery ui widget主要提供了$.widget和$.Widget两个函数对象。一个小写一个大写。
$.widget提供了对widget组件类的构造函数的创建,实现对widget类的完整属性的封装和注册功能以及继承机制。查看jquery ui的源码可以看到,jquery ui中的所有自定义widget组件都是使用$.widget函数创建的,使用$.widget函数就会生成该widget组件类的构造函数,然后使用$.widget.bridge函数来将自定义的widget组件类注册到jQuery对象上,然后在jQuery对象上就可以使用该方法了,可以使用jQuery(element).xxWidget()的方法进行调用了,其中xxWidget就是widget的name,这样就完成了对jQuery对象的扩展。一个自定义的widget类利用$.widget函数只需要实现特定的接口方法和指定widget自己私有和公有属性和方法后即可。$.widget负责对于这些函数的调用以及属性的继承机制的实现。至于需要实现哪些接口方法,这些就是$.Widget(注意是大写的W)函数对象的作用了。
$.Widget是所有jquery ui中widget组件的基类,就和javascript所有对象的基类是Object一样。$.Widget里面定义了jquery ui中定义的组件的一些基本属性和接口方法。例如里面有一些基本的属性有:
widgetName: "widget", widgetEventPrefix: "", defaultElement: "<div>", options: { disabled: false, // callbacks create: null }jquery ui中每个定义的widget中都有这些属性,也可以进行重写。
还有一些定义的widget需要实现的方法,包括:_getCreateOptions, _getCreateEventData, _create, _init, _destroy等。当然这里面有些函数可以不实现,一般情况下,_create函数是必须实现的,其余的函数可以根据需要而定。
好了,基本的原理就是这样。下面分别对$..widget和$.Widget的源码进行解析。这里以jQuery UI Widget 1.11.4版本为例。
首先看一下$..widget。
$.widget = function( name, base, prototype ) { var fullName, existingConstructor, constructor, basePrototype, // proxiedPrototype allows the provided prototype to remain unmodified // so that it can be used as a mixin for multiple widgets (#8876) proxiedPrototype = {}, //一般的widget在定义的时候,都是xxx.xxxx的形式,分别表示namespace和widgetName //例如ui.tab,namespace='ui';name='tab';fullName='ui-tab'; namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; //如果prototype没有指定,那么就以$.Widget作为基类 if ( !prototype ) { prototype = base; base = $.Widget; } //$.expr.pseudos === $.expr[":"] 用来创建自定义的伪类选择器,可以直接使用fullName选择出已经创建该plugin的element //关于$.expr.pseudos原理请见http://www.tuicool.com/articles/NzYRZvq // create selector for plugin $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; existingConstructor = $[ namespace ][ name ]; constructor = $[ namespace ][ name ] = function( options, element ) { //加这句代码的意思就是允许不用new constructor()的形式,而是可以用constructor()直接创建plugin的对象 //因为$.widget函数返回的就是constructor函数 //因为直接用constructor()创建的话,this就可能不是当前正在创建的对象,例如可能是window对象。这样this上就不会有createWidget函数 //因此在这里面重新调用new constructor()函数来将this指针指向为当前的对象 //因为所有的jquery ui的widget都是继承$.Widget,所以此时的this肯定就会有createWidget了,所以不会死循环 //对于javascript new的原理可以参考http://www.cnblogs.com/purediy/archive/2012/09/12/2682490.html //以及javascript原型原理http://www.cnblogs.com/wilber2013/p/4924309.html // allow instantiation without "new" keyword if ( !this._createWidget ) { return new constructor( options, element ); } // allow instantiation without initializing for simple inheritance // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; //也许之前已经定义过该plugin,这里将已有的构造函数的属性拷贝到现在新的构造函数上 // extend with the existing constructor to carry over any static properties $.extend( constructor, existingConstructor, { version: prototype.version, // copy the object used to create the prototype in case we need to // redefine the widget later _proto: $.extend( {}, prototype ), // track widgets that inherit from this widget in case this widget is // redefined after a widget inherits from it _childConstructors: [] }); //创建父类的空对象,这样父类中所有的属性都是默认值,因而可以实现属性的继承 basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from basePrototype.options = $.widget.extend( {}, basePrototype.options ); $.each( prototype, function( prop, value ) { if ( !$.isFunction( value ) ) { proxiedPrototype[ prop ] = value; return; } //对该widget内部定义的方法都进行了代理类的封装 proxiedPrototype[ prop ] = (function() { var _super = function() { return base.prototype[ prop ].apply( this, arguments ); }, _superApply = function( args ) { return base.prototype[ prop ].apply( this, args ); }; return function() { var __super = this._super, __superApply = this._superApply, returnValue; this._super = _super; this._superApply = _superApply; returnValue = value.apply( this, arguments ); this._super = __super; this._superApply = __superApply; return returnValue; }; })(); }); constructor.prototype = $.widget.extend( basePrototype, { // TODO: remove support for widgetEventPrefix // always use the name + a colon as the prefix, e.g., draggable:start // don't prefix for widgets that aren't DOM-based //widgetEventPrefix为name 这样该widget的所有事件就会widgetEventPrefix+event, //使用bind监听该widget的事件时,需要bind(widgetEventPrefix+event...) widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name }, proxiedPrototype, { constructor: constructor, //每个函数的原型都需要有constructor属性来指向构造函数本身 namespace: namespace, widgetName: name, widgetFullName: fullName }); //在重新定义了父类的构造函数后,所有继承该类的子类的构造函数都要重新定义 // If this widget is being redefined then we need to find all widgets that // are inheriting from it and redefine all of them so that they inherit from // the new version of this widget. We're essentially trying to replace one // level in the prototype chain. if ( existingConstructor ) { $.each( existingConstructor._childConstructors, function( i, child ) { var childPrototype = child.prototype; // redefine the child widget using the same prototype that was // originally used, but inherit from the new version of the base $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); }); // remove the list of existing child constructors from the old constructor // so the old child constructors can be garbage collected delete existingConstructor._childConstructors; } else { base._childConstructors.push( constructor ); } //将自定义的plugin注册到jQuery对象上,这样就可以使用jQuery创建对象的方式进行plugin对象的创建 $.widget.bridge( name, constructor ); return constructor; };
这里面用到的bridge函数正如上面所说,将widget注册在jQuery对象上,在bridge函数内部创建了$.fn[name]函数。函数源码如下:
$.widget.bridge = function( name, object ) { var fullName = object.prototype.widgetFullName || name; //使用widget的name将widget注册在jQuery对象上 $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", args = widget_slice.call( arguments, 1 ), //returnValue首先赋值为s,即当前的jQuery实例对象,是为了统一jquery的链式操作 returnValue = this; //如果传进来的参数是string,说明是调用widget内部的指定的函数 if ( isMethodCall ) { this.each(function() { var methodValue, instance = $.data( this, fullName ); //如果传进来的参数是"instance",直接返回在该元素上创建的该widget实例 if ( options === "instance" ) { returnValue = instance; return false; } if ( !instance ) { return $.error( "cannot call methods on " + name + " prior to initialization; " + "attempted to call method '" + options + "'" ); } //当该参数指定的函数不存在,或者是以“_”开头,说明是内部私有函数,不能调用 if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { return $.error( "no such method '" + options + "' for " + name + " widget instance" ); } methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { returnValue = methodValue && methodValue.jquery ? //说明methodValue是一个JQuery实例对象 //使用pushStack重新把returnValue变成一个jQuery实例对象, //methodValue本来就是个jQuery对象,为什么不直接把returnValue直接赋值给returnValue呢 //这是因为链式操作中,可能methodValue的prevObject不是当前的this对象, //这样如果进行end()操作就不会回退到当前的this jQuery实例对象,破坏了操作的延续性 //而pushStack的作用就是将传入的dom元素集合压入一个新的JQuery对象实例中, //并把这个新的JQuery对象实例的prevObject赋值为pushStack函数的调用者 returnValue.pushStack( methodValue.get() ) : //methodValue.get()返回dom元素数组 methodValue; return false; } }); } else { // Allow multiple hashes to be passed on init if ( args.length ) { options = $.widget.extend.apply( null, [ options ].concat(args) ); } this.each(function() { var instance = $.data( this, fullName ); //如果该元素上已经创建该widget实例对象,就重新设置option参数,并调用_init函数 if ( instance ) { instance.option( options || {} ); if ( instance._init ) { instance._init(); } } else { //调用构造函数创建该widget的实例对象,并且把对象存入该dom元素上 $.data( this, fullName, new object( options, this ) ); } }); } return returnValue; }; };
//空的构造函数 $.Widget = function( /* options, element */ ) {}; //用于记录继承该类的子类 $.Widget._childConstructors = []; //每个函数对象都有原型对象,可以实现继承机制 //这里面包括了widget所具有的一些基本属性和方法。所有的jquery ui widget都会拥有这些属性和方法 $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", defaultElement: "<div>", options: { disabled: false, // callbacks create: null }, //真正的初始化方法,每个widget都会调用该方法来执行初始化创建 _createWidget: function( options, element ) { element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); this.uuid = widget_uuid++; this.eventNamespace = "." + this.widgetName + this.uuid; this.bindings = $(); this.hoverable = $(); this.focusable = $(); if ( element !== this ) { $.data( element, this.widgetFullName, this ); //使用Widget的_on函数注册remove事件处理函数,第一个参数为true,表示不禁用该事件响应函数 this._on( true, this.element, { remove: function( event ) { if ( event.target === element ) { this.destroy(); } } }); this.document = $( element.style ? // element within the document element.ownerDocument : // element is window or document element.document || element ); this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); } this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); //这里调用的_create函数执行具体的初始化创建操作 this._create(); this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, _getCreateOptions: $.noop, _getCreateEventData: $.noop, _create: $.noop, _init: $.noop, destroy: function() { this._destroy(); // we can probably remove the unbind calls in 2.0 // all event bindings should go through this._on() this.element .unbind( this.eventNamespace ) .removeData( this.widgetFullName ) // support: jquery <1.6.3 // http://bugs.jquery.com/ticket/9413 .removeData( $.camelCase( this.widgetFullName ) ); this.widget() .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( this.widgetFullName + "-disabled " + "ui-state-disabled" ); // clean up events and states this.bindings.unbind( this.eventNamespace ); this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); }, _destroy: $.noop, widget: function() { return this.element; }, //获取或设置特定的option value.支持使用“xx.xxx”的形式来操作深层次的对象 option: function( key, value ) { var options = key, parts, curOption, i; //直接调用option(),不指定参数,就直接返回该widget的options对象 if ( arguments.length === 0 ) { // don't return a reference to the internal hash return $.widget.extend( {}, this.options ); } if ( typeof key === "string" ) { // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; parts = key.split( "." ); key = parts.shift(); if ( parts.length ) { curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); for ( i = 0; i < parts.length - 1; i++ ) { curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; curOption = curOption[ parts[ i ] ]; } key = parts.pop(); if ( arguments.length === 1 ) { return curOption[ key ] === undefined ? null : curOption[ key ]; } curOption[ key ] = value; } else { if ( arguments.length === 1 ) { return this.options[ key ] === undefined ? null : this.options[ key ]; } options[ key ] = value; } } this._setOptions( options ); return this; }, _setOptions: function( options ) { var key; for ( key in options ) { this._setOption( key, options[ key ] ); } return this; }, _setOption: function( key, value ) { this.options[ key ] = value; if ( key === "disabled" ) { this.widget() .toggleClass( this.widgetFullName + "-disabled", !!value ); // If the widget is becoming disabled, then nothing is interactive if ( value ) { this.hoverable.removeClass( "ui-state-hover" ); this.focusable.removeClass( "ui-state-focus" ); } } return this; }, enable: function() { return this._setOptions({ disabled: false }); }, disable: function() { return this._setOptions({ disabled: true }); }, //事件监听注册函数 suppressDisabledCheck表示是否禁止该事件禁用的检查 _on: function( suppressDisabledCheck, element, handlers ) { var delegateElement, instance = this; // no suppressDisabledCheck flag, shuffle arguments if ( typeof suppressDisabledCheck !== "boolean" ) { handlers = element; element = suppressDisabledCheck; suppressDisabledCheck = false; } // no element argument, shuffle and use this.element if ( !handlers ) { handlers = element; element = this.element; delegateElement = this.widget(); } else { element = delegateElement = $( element ); this.bindings = this.bindings.add( element ); } $.each( handlers, function( event, handler ) { function handlerProxy() { // allow widgets to customize the disabled handling // - disabled as an array instead of boolean // - disabled class as method for disabling individual parts if ( !suppressDisabledCheck && //如果对该事件的禁用与否进行检查且该instance的disable为true,那么就不会响应该事件,响应函数不起作用 ( instance.options.disabled === true || $( this ).hasClass( "ui-state-disabled" ) ) ) { return; } return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } // copy the guid so direct unbinding works if ( typeof handler !== "string" ) { handlerProxy.guid = handler.guid = handler.guid || handlerProxy.guid || $.guid++; } //对事件监听函数进行绑定。传进来的event参数可能是“eventName selector”的形式 var match = event.match( /^([\w:-]*)\s*(.*)$/ ), eventName = match[1] + instance.eventNamespace, selector = match[2]; if ( selector ) { //如果有selector则表示对delegateElement的某子元素上的事件进行监听并注册事件响应函数 delegateElement.delegate( selector, eventName, handlerProxy ); } else { element.bind( eventName, handlerProxy ); } }); }, //取消事件的监听 _off: function( element, eventName ) { eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; element.unbind( eventName ).undelegate( eventName ); // Clear the stack to avoid memory leaks (#10056) this.bindings = $( this.bindings.not( element ).get() ); this.focusable = $( this.focusable.not( element ).get() ); this.hoverable = $( this.hoverable.not( element ).get() ); }, //延迟调用函数 _delay: function( handler, delay ) { function handlerProxy() { return ( typeof handler === "string" ? instance[ handler ] : handler ) .apply( instance, arguments ); } var instance = this; return setTimeout( handlerProxy, delay || 0 ); }, //增加鼠标悬停事件响应函数 _hoverable: function( element ) { this.hoverable = this.hoverable.add( element ); this._on( element, { mouseenter: function( event ) { $( event.currentTarget ).addClass( "ui-state-hover" ); }, mouseleave: function( event ) { $( event.currentTarget ).removeClass( "ui-state-hover" ); } }); }, //增加焦点事件响应函数 _focusable: function( element ) { this.focusable = this.focusable.add( element ); this._on( element, { focusin: function( event ) { $( event.currentTarget ).addClass( "ui-state-focus" ); }, focusout: function( event ) { $( event.currentTarget ).removeClass( "ui-state-focus" ); } }); }, //触发事件函数 type表示事件的类型,event表示具体的事件对象,可为空 _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; //直接获取该类型的事件的回调函数 data = data || {}; event = $.Event( event ); event.type = ( type === this.widgetEventPrefix ? type : this.widgetEventPrefix + type ).toLowerCase(); // the original event may come from any element // so we need to reset the target on the new event event.target = this.element[ 0 ]; // copy original event properties over to the new event orig = event.originalEvent; if ( orig ) { for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } } this.element.trigger( event, data ); //触发事件 return !( $.isFunction( callback ) && callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } };