Showing posts with label Jquery Widget factory demystified. Show all posts
Showing posts with label Jquery Widget factory demystified. Show all posts

Monday, May 14, 2012

JQuery Widget Factory

jquery.ui.widget, this script attaches 'widget' property to jquery function object.

jquery.widget =  function( name, base, prototype ) {
    var namespace = name.split( "." )[ 0 ],
        fullName;
    name = name.split( "." )[ 1 ];
    fullName = namespace + "-" + name;

    if ( !prototype ) {
        prototype = base;
        base = $.Widget;
    }

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

    $[ namespace ] = $[ namespace ] || {};
    $[ namespace ][ name ] = function( options, element ) {
        // allow instantiation without initializing for simple inheritance
        if ( arguments.length ) {
            this._createWidget( options, element );
        }
    };

    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 );
    $[ namespace ][ name ].prototype = $.extend( true, basePrototype, {
        namespace: namespace,
        widgetName: name,
        widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name,
        widgetBaseClass: fullName
    }, prototype );

    $.widget.bridge( name, $[ namespace ][ name ] );
};

the above function when invoked like
      jquery.widget('sekhar.mywidget',{/*custom object */})
 execution trace :

namespace = 'sekhar'
name='mywidget '

jQuery['sekhar']['mywidget']= function( options, element ) {
        // allow instantiation without initializing for simple inheritance
        if ( arguments.length ) {
            this._createWidget( options, element );
        }
    };

A object is created using the constructor function $.Widget (capital 'W') .This object has default implementation.

This object is merged with the custom object you pass while creating the widget.This way you can override default implementations.

$.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();
        this._trigger( "create" );
        this._init();
    },
    _getCreateOptions: function() {
        return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
    },
    _create: function() {},
    _init: function() {},

    destroy: function() {
        alert('destroy');
        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 );
        }

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

    _trigger: function( type, event, data ) {
       alert("triiger "+event)
        var prop, orig,
            callback = this.options[ type ];
        alert('callback' + callback);
        alert('trigger1' + data)
        data = data || {};
        alert('trigger2' + 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
        alert('eventtype ' + event.type);
        event.target = this.element[ 0 ];

        // copy original event properties over to the new event
        orig = event.originalEvent;
        alert('orig' + orig);
        if ( orig ) {
            for ( prop in orig ) {
                if ( !( prop in event ) ) {
                    event[ prop ] = orig[ prop ];
                }
            }
        }

        this.element.trigger( event, data );
        alert('trigger3' + data)
        return !( $.isFunction(callback) &&
            callback.call( this.element[0], event, data ) === false ||
            event.isDefaultPrevented() );
    }
};

Important function :
Following function add a property to jquery prototype object,
jquery.prototype['myWidget'] = function (){} with scope containing the function object stored in jquery['sekhar']['myWidget'].

$('#id').myWidget({/*options*/})  the function attached to jquery prototype is executed.







$.widget.bridge = function( name, object ) {
    $.fn[ name ] = function( 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;

        // 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 {
            this.each(function() {              
                var instance = $.data( this, name );
                if ( instance ) {
                    instance.option( options || {} )._init();
                } else {
                    $.data( this, name, new object( options, this ) );
                }
            });
        }

        return returnValue;
    };
};