`
weakfi
  • 浏览: 97069 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

jquery.ui.widget 工作原理分析

 
阅读更多

 

/*!
 * jQuery UI Widget 1.8.15
 * $Id: jquery.ui.widget.js,v 1.3 2011/12/06 07:19:29 licongping Exp $
 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * http://docs.jquery.com/UI/Widget
 */
(function( $, undefined ) {

// jQuery 1.4+
if ( $.cleanData ) {
	var _cleanData = $.cleanData;
	// 重写cleanData方法,调用后触发每个元素的remove事件
	$.cleanData = function( elems ) {
		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
			$( elem ).triggerHandler( "remove" );
		}
		_cleanData( elems );
	};
} else {
	var _remove = $.fn.remove;
	$.fn.remove = function( selector, keepData ) {
		return this.each(function() {
			if ( !keepData ) {
				if ( !selector || $.filter( selector, [ this ] ).length ) {
					$( "*", this ).add( [ this ] ).each(function() {
						$( this ).triggerHandler( "remove" );
					});
				}
			}
			return _remove.call( $(this), selector, keepData );
		});
	};
}

$.widget = function( name, base, prototype ) {
	var namespace = name.split( "." )[ 0 ],
		fullName;
	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;
	// 例如参数name='om.tabs',变成namespace='om',name='tabs',fullName='om-tabs'

	// base默认为Widget类,组件默认会继承base类的所有方法
	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName ] = function( elem ) {
		return !!$.data( elem, name );
	};

	// 创建命名空间$.om.tabs
	$[ namespace ] = $[ namespace ] || {};
	$[ namespace ][ name ] = function( options, element ) {
		// allow instantiation without initializing for simple inheritance
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};

	// 初始化父类,一般调用了$.Widget
	var 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
//	$.each( basePrototype, function( key, val ) {
//		if ( $.isPlainObject(val) ) {
//			basePrototype[ key ] = $.extend( {}, val );
//		}
//	});
	basePrototype.options = $.extend( true, {}, basePrototype.options );
	// 给om.tabs继承父类的所有原型方法和参数
	$[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
		namespace: namespace,
		widgetName: name,
		// 组件的事件名前缀,调用_trigger的时候会默认给trigger的事件加上前缀
		// 例如_trigger('create')实际会触发'tabscreate'事件
		widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
		widgetBaseClass: fullName
	}, prototype );

	// 把tabs方法挂到jquery对象上,也就是$('#tab1').tabs();
	$.widget.bridge( name, $[ namespace ][ name ] );
};

$.widget.bridge = function( name, object ) {
	$.fn[ name ] = function( options ) {
		// 如果tabs方法第一个参数是string类型,则认为是调用组件的方法,否则调用options方法
		var isMethodCall = typeof options === "string",
			args = Array.prototype.slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.extend.apply( null, [ true, options ].concat(args) ) :
			options;

		// '_'开头的方法被认为是内部方法,不会被执行,如$('#tab1').tabs('_init')
		// prevent calls to internal methods
		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
			return returnValue;
		}

		if ( isMethodCall ) {
			this.each(function() {
				// 执行组件方法
				var instance = $.data( this, name ),
					methodValue = instance && $.isFunction( instance[options] ) ?
						instance[ options ].apply( instance, args ) :
						instance;
				// TODO: add this back in 1.9 and use $.error() (see #5972)
//				if ( !instance ) {
//					throw "cannot call methods on " + name + " prior to initialization; " +
//						"attempted to call method '" + options + "'";
//				}
//				if ( !$.isFunction( instance[options] ) ) {
//					throw "no such method '" + options + "' for " + name + " widget instance";
//				}
//				var methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue;
					return false;
				}
			});
		} else {
			// 调用组件的options方法
			this.each(function() {
				var instance = $.data( this, name );
				if ( instance ) {
					// 设置options后再次调用_init方法,第一次调用是在_createWidget方法里面。这个方法需要开发者去实现。
					// 主要是当改变组件中某些参数后可能需要对组件进行重画
					instance.option( options || {} )._init();
				} else {
					// 没有实例的话,给元素绑定一个实例。注意这里的this是dom,object是模块类  
					$.data( this, name, new object( options, this ) );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( options, element ) {
	// allow instantiation without initializing for simple inheritance
	if ( arguments.length ) {//如果有参数,调用初始化函数  
		this._createWidget( options, element );
	}
};

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	options: {
		disabled: false
	},
	_createWidget: function( options, element ) {
		// $.widget.bridge stores the plugin instance, but we do it anyway
		// so that it's stored even before the _create function runs
		$.data( element, this.widgetName, this );
		this.element = $( element );
		this.options = $.extend( true, {},
			this.options,
			this._getCreateOptions(),
			options );

		var self = this;
		this.element.bind( "remove." + this.widgetName, function() {
			self.destroy();
		});
		// 开发者实现
		this._create();
		// 如果绑定了初始化的回调函数,会在这里触发。注意绑定的事件名是需要加上前缀的,如$('#tab1').bind('tabscreate',function(){});
		this._trigger( "create" );
		// 开发者实现
		this._init();
	},
	_getCreateOptions: function() {
		return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
	},
	_create: function() {},
	_init: function() {},

	destroy: function() {
		this.element
			.unbind( "." + this.widgetName )
			.removeData( this.widgetName );
		this.widget()
			.unbind( "." + this.widgetName )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetBaseClass + "-disabled " +
				"ui-state-disabled" );
	},

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.extend( {}, this.options ); // 返回组件的options
		}

		if  (typeof key === "string" ) {
			if ( value === undefined ) {
				return this.options[ key ]; // 获取值
			}
			options = {};
			options[ key ] = value;
		}

		this._setOptions( options ); // 设置值

		return this;
	},
	_setOptions: function( options ) {
		var self = this;
		$.each( options, function( key, value ) {
			self._setOption( key, value );
		});

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				[ value ? "addClass" : "removeClass"](
					this.widgetBaseClass + "-disabled" + " " +
					"ui-state-disabled" )
				.attr( "aria-disabled", value );
		}

		return this;
	},

	enable: function() {
		return this._setOption( "disabled", false );
	},
	disable: function() {
		return this._setOption( "disabled", true );
	},

	// $.widget中优化过的trigger方法。可以同时调用config中的方法和bind的方法。
	// 即可以用两个方式去给组件绑定事件。如$("tabs").omTabs({"change":function(){//handler}});或者$("tabs").bind("tabschange",function(){//handler});
	_trigger: function( type, event, data ) {
		// 调用初始化配置config中的回调方法
		var callback = this.options[ type ];
		// 封装js标准event对象为jquery的Event对象
		event = $.Event( event );
		// 给事件名称加上前缀,前缀默认是组件的名字。如_trigger("change")实际会触发"tabschange"事件
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		data = data || {};
		// copy original event properties over to the new event
		// this would happen if we could call $.event.fix instead of $.Event
		// but we don't have a way to force an event to be fixed multiple times
		if ( event.originalEvent ) {
			for ( var i = $.event.props.length, prop; i; ) {
				prop = $.event.props[ --i ];
				event[ prop ] = event.originalEvent[ prop ];
			}
		}
		// 触发element中绑定的事件
		this.element.trigger( event, data );
		return !( $.isFunction(callback) &&
			callback.call( this.element[0], event, data ) === false ||
			event.isDefaultPrevented() );
	}
};

})( jQuery );
分享到:
评论
1 楼 qkjava 2015-01-22  
好文章!好文章!

相关推荐

    jquery ui 1.8.2 code

    `js` 文件夹存放 JavaScript 文件,其中 `ui/jquery.ui.core.js` 是核心模块,其他如 `ui/jquery.ui.widget.js`、`ui/jquery.ui.mouse.js` 等则为各个组件的具体实现;`css` 文件夹包含样式表,用于定义组件的视觉...

    jquery-ui-1.8.16.custom

    通过分析这个文件,开发者可以学习到如何正确地链接jQuery库、jQuery UI库以及自定义的CSS和JavaScript文件,以确保UI组件的正常运行。 接着,"js"目录包含了jQuery UI的核心JavaScript文件。这里通常有"ui/jquery....

    jQuery widget设计文档

    在本篇文章中,我们将深入探讨jQuery widget的设计原理、使用方法以及相关的最佳实践。 首先,jQuery widget的设计遵循了面向对象的原则,允许开发者定义一组公共方法和属性,这些方法和属性可以通过options进行...

    jqueryUI(jquery演示部分)

    通过分析这些文件,我们可以理解每个组件的工作原理和实现方式。 `css` 文件夹则包含了样式表,用于定义组件的外观和布局。jQuery UI 提供了一套完整的主题系统,开发者可以通过修改或自定义主题CSS来改变组件的...

    jquery 拖动.rar

    我们将通过分析给定的"jquery 拖动.rar"文件中的代码示例,了解其工作原理和自定义配置。 首先,让我们了解jQuery拖放功能的基本概念。jQuery UI库提供了强大的拖放插件,使得实现拖放操作变得简单易行。但在本案例...

    jQuery Mobile进阶

    通过对《JQUERYMOBILE进阶》的深入探讨,我们不仅能够掌握jQuery Mobile的工作原理,还能学到一系列开发实践中的宝贵经验。无论是初学者还是资深开发者,都能从中获得启示,提升自己在移动Web开发领域的技能。最后,...

    jQuery Combobox 扩展 (select+autocomplete)20110730更新

    - **自定义样式**: 基于jQuery UI,Combobox可以轻松地与其他UI组件保持一致的外观和风格,通过CSS调整主题以适应网站设计。 - **键盘导航**: 支持键盘操作,如上下键选择选项,回车键确认选择,ESC键关闭下拉列表,...

    基于jquery插件实现拖拽删除图片功能

    我们将分析给出的代码实例,了解其工作原理,并讨论相关的核心jQuery和JavaScript概念。 首先,我们需要引入jQuery库以及jQuery UI的相关组件,包括`jquery-ui-core.js`, `jquery-ui-widget.js`, `jquery-ui-mouse....

    jquery mobile

    通过阅读和理解源码,开发者可以更好地了解其工作原理,进行定制化开发或优化性能。 **工具支持** jQuery Mobile 社区提供了多种工具来辅助开发,如: 1. **Themeroller**:在线工具,允许用户自定义主题颜色和...

    widget:网站上的 js 小部件

    本文将深入探讨JavaScript小部件的概念、工作原理以及如何创建和使用。 JavaScript小部件,通常简称为“小部件”,是独立的、可重用的代码模块,能够嵌入到网页中以实现特定的功能。这些功能可以包括天气预报、社交...

    Tab控件

    **Tab控件详解** 在软件开发中,Tab控件是一种常见的用户界面元素,它允许用户在多个页面或视图之间切换,每个页面通常称为一个标签页...理解其工作原理、实现方式以及设计原则,将有助于开发者创建更优秀的用户界面。

    Ext中文手册

    深入EXT源码有助于理解其工作原理和优化应用性能。EXT的源码组织清晰,模块化良好,开发者可以通过阅读源码学习EXT的内部机制。 适配器Adapters: 适配器是EXT与不同浏览器或JavaScript库交互的关键。EXT支持多种...

Global site tag (gtag.js) - Google Analytics