jQuery UI Widget(1.8.1)工做原理

/*!
 * jQuery UI Widget 1.8.1
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Widget
 */
(function( $ ) {
 
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" );
                });
            }
        }
        //dom元素在被刪除前,觸發一下remove事件,jquery框架自己沒有對元素刪除綁定事件
        return _remove.call( $(this), selector, keepData );
    });
};
 
$.widget = function( name, base, prototype ) {
    var namespace = name.split( "." )[ 0 ],
        fullName;
    name = name.split( "." )[ 1 ];
    fullName = namespace + "-" + name;
    //好比ui.tab,上面的name='tab';fullName='ui-tab';
 
    if ( !prototype ) {
        prototype = base;
        base = $.Widget;
    }
    //若是沒有prototype,那麼prototype就是base參數,實際base默認爲$.Widget
 
    // create selector for plugin
    $.expr[ ":" ][ fullName ] = function( elem ) {
        return !!$.data( elem, name );
    };
 
    $[ namespace ] = $[ namespace ] || {};//是否有命名空間
    $[ namespace ][ name ] = function( options, element ) {//根據上面的例子,即初始化了$.ui.tab=func
        // allow instantiation without initializing for simple inheritance
        if ( arguments.length ) {
            this._createWidget( options, element );
        }
    };
 
    var basePrototype = new base();//初始化,通常都是調用了new $.Widget()
    // 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( {}, basePrototype.options );//初始化options值,注意不須要深度拷貝
    $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
        namespace: namespace,
        widgetName: name,
        widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
        widgetBaseClass: fullName
    }, prototype );
    //爲新的ui模塊建立原型,使用深度拷貝,在basePrototype上擴展一些模塊基本信息,在擴展prototype,好比ui.tabs.js中就是tab的擁有各類方法的大對象
 
    $.widget.bridge( name, $[ namespace ][ name ] );//將此方法掛在jQuery對象上
};
 
$.widget.bridge = function( name, object ) {
    $.fn[ name ] = function( options ) {
        var isMethodCall = typeof options === "string",
            args = Array.prototype.slice.call( arguments, 1 ),
            returnValue = this;
        //若是第一個參數是string類型,就認爲是調用模塊方法
        //剩下的參數做爲方法的參數,後面會用到
 
        // allow multiple hashes to be passed on init
        options = !isMethodCall && args.length ?
            $.extend.apply( null, [ true, options ].concat(args) ) :
            options;
        //能夠簡單認爲是$.extend(true,options,args[0],...),args能夠是一個參數或是數組
 
        // prevent calls to internal methods
        if ( isMethodCall && options.substring( 0, 1 ) === "_" ) {
            return returnValue;
        }
        //開頭帶下劃線的方法都是私有方法,不讓調用
 
        if ( isMethodCall ) {//若是是調用函數
            this.each(function() {
                var instance = $.data( this, name ),//獲得實例,實例做爲一個數據和元素關聯上
                    methodValue = instance && $.isFunction( instance[options] ) ?
                        instance[ options ].apply( instance, args ) ://若是實例和方法均存在,調用方法,把args做爲參數傳進去
                        instance;//不然返回undefined
                if ( methodValue !== instance && methodValue !== undefined ) {//若是methodValue不是jquery對象也不是undefined
                    returnValue = methodValue;
                    return false;//跳出each,通常獲取options的值會走這個分支
                }
            });
        } else {//不是函數調用的話
            this.each(function() {
                var instance = $.data( this, name );
                if ( instance ) {//實例存在
                    if ( options ) {//有參數
                        instance.option( options );//調用option函數,通常是設置狀態之類的操做
                    }
                    instance._init();//再次調用此函數,根據options調整
                } else {
                    $.data( this, name, new object( options, this ) );
                    //沒有實例的話,給元素綁定一個實例。注意這裏的this是dom,object是模塊類
                }
            });
        }
 
        return returnValue;//返回,有多是jquery對象,有多是其餘值
    };
};
 
$.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
        this.element = $( element ).data( this.widgetName, this );//緩存實例,保存jquery對象
        this.options = $.extend( true, {},
            this.options,
            $.metadata && $.metadata.get( element )[ this.widgetName ],
            options );//參數處理
 
        var self = this;
        this.element.bind( "remove." + this.widgetName, function() {
            self.destroy();
        });//註冊銷燬事件
 
        this._create();//建立
        this._init();//初始化
    },
    _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() {//返回jquery對象
        return this.element;
    },
 
    option: function( key, value ) {//設置選項函數
        var options = key,
            self = this;
 
        if ( arguments.length === 0 ) {
            // don't return a reference to the internal hash
            return $.extend( {}, self.options );//返回一個新的對象,不是內部數據的引用
        }
 
        if  (typeof key === "string" ) {
            if ( value === undefined ) {
                return this.options[ key ];//取值
            }
            options = {};
            options[ key ] = value;//設置值
        }
 
        $.each( options, function( key, value ) {
            self._setOption( key, value );//調用內部的_setOption
        });
 
        return self;
    },
    _setOption: function( key, value ) {
        this.options[ key ] = value;
 
        if ( key === "disabled" ) {//增長或是去除className
            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 );
    },
 
    _trigger: function( type, event, data ) {
        var callback = this.options[ type ];
 
        event = $.Event( event );
        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 ) {//把原始的event屬性從新賦到event變量上
            for ( var i = $.event.props.length, prop; i; ) {
                prop = $.event.props[ --i ];
                event[ prop ] = event.originalEvent[ prop ];
            }
        }
 
        this.element.trigger( event, data );
 
        return !( $.isFunction(callback) &&
            callback.call( this.element[0], event, data ) === false ||
            event.isDefaultPrevented() );
    }
};
 
})( jQuery );

上面是jquery.ui.widget.js的源碼,jquery ui的全部模塊都是基於其中的widget方法進行擴展,使用統一的命名規範和編碼風格。 
先來講一下原理: 
$.widget此函數完成了對jQuery自己的擴展,根據第一個參數來肯定模塊的命名空間和函數名;第二個參數肯定模塊的基類(默認是$.Widget);第三個參數實現模塊自己的方法。好比標籤切換插件jquery.ui.tabs.js中開始: 
$.widget(「ui.tabs」, {…});//這裏只有兩個參數,那麼基類就默認是$.Widget 
第一個參數:」ui.tabs」用來表示在jQuery上選擇(或增長)一個命名空間,即若是jQuery.ui不存在,則定義jQuery.ui = {},而後在jQuery.ui上增長一個函數,名稱爲tabs.最後調用$.widget.bridge將tabs方法掛在jQuery對象上。這樣,全部的jquery對象將擁有tabs方法。 

注意:jquery ui有嚴格的命名規範,每一個控件對外只暴露一個藉口。控件全部方法或屬性經過向此藉口傳遞不一樣參數來調用和獲取。 

jquery ui的大部分控件是基於$.Widget基類實現的。因此通常咱們作控件是都要重寫$.Widget類中的一些方法。通常來講,一個ui控件須要實現下列的方法或屬性: 
屬性: 
options 用來緩存控件各項參數 
私有方法,使用「$(xx).tabs(私有方法)」這種方式來調用私有方法時會馬上返回,調用不能成功: 
_create 控件初始化調用,屢次調用$(xx).tabs()這樣不帶參數的方法只會執行一次 
_init 通常不用實現,默認爲空函數,每次「$(xx).tabs()」這樣調用時會調用此方法 
_setOption 「$(xx).tabs(‘option’,xxx)」這種調用方式會調用此方法 
公開方法: 
destroy 銷燬模塊 
option 設置或獲取參數 
enable 啓用模塊功能 
disable 禁用功能 

幾乎全部的jquery ui控件都會重寫這些接口,同時增長控件相關的私有或公有方法。 

記住,jquery ui的實例是和元素關聯起來的,做爲數據保存起來了。暴露給用戶使用的只是jquery對象上增長的方法。通常咱們不須要獲取ui的實例。jquery

轉自:http://xj19891016.iteye.com/blog/789201#數組

相關文章
相關標籤/搜索