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() );
}
};