jquery.ui.widget.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /*!
  2. * jQuery UI Widget v1.9 stable
  3. * http://jqueryui.com
  4. *
  5. * Copyright 2012 jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. *
  9. * http://api.jqueryui.com/jQuery.widget/
  10. */
  11. (function( $, undefined ) {
  12. var uuid = 0,
  13. slice = Array.prototype.slice,
  14. _cleanData = $.cleanData;
  15. $.cleanData = function( elems ) {
  16. for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
  17. try {
  18. $( elem ).triggerHandler( "remove" );
  19. // http://bugs.jquery.com/ticket/8235
  20. } catch( e ) {}
  21. }
  22. _cleanData( elems );
  23. };
  24. $.widget = function( name, base, prototype ) {
  25. var fullName, existingConstructor, constructor, basePrototype,
  26. namespace = name.split( "." )[ 0 ];
  27. name = name.split( "." )[ 1 ];
  28. fullName = namespace + "-" + name;
  29. if ( !prototype ) {
  30. prototype = base;
  31. base = $.Widget;
  32. }
  33. // create selector for plugin
  34. $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
  35. return !!$.data( elem, fullName );
  36. };
  37. $[ namespace ] = $[ namespace ] || {};
  38. existingConstructor = $[ namespace ][ name ];
  39. constructor = $[ namespace ][ name ] = function( options, element ) {
  40. // allow instantiation without "new" keyword
  41. if ( !this._createWidget ) {
  42. return new constructor( options, element );
  43. }
  44. // allow instantiation without initializing for simple inheritance
  45. // must use "new" keyword (the code above always passes args)
  46. if ( arguments.length ) {
  47. this._createWidget( options, element );
  48. }
  49. };
  50. // extend with the existing constructor to carry over any static properties
  51. $.extend( constructor, existingConstructor, {
  52. version: prototype.version,
  53. // copy the object used to create the prototype in case we need to
  54. // redefine the widget later
  55. _proto: $.extend( {}, prototype ),
  56. // track widgets that inherit from this widget in case this widget is
  57. // redefined after a widget inherits from it
  58. _childConstructors: []
  59. });
  60. basePrototype = new base();
  61. // we need to make the options hash a property directly on the new instance
  62. // otherwise we'll modify the options hash on the prototype that we're
  63. // inheriting from
  64. basePrototype.options = $.widget.extend( {}, basePrototype.options );
  65. $.each( prototype, function( prop, value ) {
  66. if ( $.isFunction( value ) ) {
  67. prototype[ prop ] = (function() {
  68. var _super = function() {
  69. return base.prototype[ prop ].apply( this, arguments );
  70. },
  71. _superApply = function( args ) {
  72. return base.prototype[ prop ].apply( this, args );
  73. };
  74. return function() {
  75. var __super = this._super,
  76. __superApply = this._superApply,
  77. returnValue;
  78. this._super = _super;
  79. this._superApply = _superApply;
  80. returnValue = value.apply( this, arguments );
  81. this._super = __super;
  82. this._superApply = __superApply;
  83. return returnValue;
  84. };
  85. })();
  86. }
  87. });
  88. constructor.prototype = $.widget.extend( basePrototype, {
  89. // TODO: remove support for widgetEventPrefix
  90. // always use the name + a colon as the prefix, e.g., draggable:start
  91. // don't prefix for widgets that aren't DOM-based
  92. widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
  93. }, prototype, {
  94. constructor: constructor,
  95. namespace: namespace,
  96. widgetName: name,
  97. // TODO remove widgetBaseClass, see #8155
  98. widgetBaseClass: fullName,
  99. widgetFullName: fullName
  100. });
  101. // If this widget is being redefined then we need to find all widgets that
  102. // are inheriting from it and redefine all of them so that they inherit from
  103. // the new version of this widget. We're essentially trying to replace one
  104. // level in the prototype chain.
  105. if ( existingConstructor ) {
  106. $.each( existingConstructor._childConstructors, function( i, child ) {
  107. var childPrototype = child.prototype;
  108. // redefine the child widget using the same prototype that was
  109. // originally used, but inherit from the new version of the base
  110. $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
  111. });
  112. // remove the list of existing child constructors from the old constructor
  113. // so the old child constructors can be garbage collected
  114. delete existingConstructor._childConstructors;
  115. } else {
  116. base._childConstructors.push( constructor );
  117. }
  118. $.widget.bridge( name, constructor );
  119. };
  120. $.widget.extend = function( target ) {
  121. var input = slice.call( arguments, 1 ),
  122. inputIndex = 0,
  123. inputLength = input.length,
  124. key,
  125. value;
  126. for ( ; inputIndex < inputLength; inputIndex++ ) {
  127. for ( key in input[ inputIndex ] ) {
  128. value = input[ inputIndex ][ key ];
  129. if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
  130. // Clone objects
  131. if ( $.isPlainObject( value ) ) {
  132. target[ key ] = $.isPlainObject( target[ key ] ) ?
  133. $.widget.extend( {}, target[ key ], value ) :
  134. // Don't extend strings, arrays, etc. with objects
  135. $.widget.extend( {}, value );
  136. // Copy everything else by reference
  137. } else {
  138. target[ key ] = value;
  139. }
  140. }
  141. }
  142. }
  143. return target;
  144. };
  145. $.widget.bridge = function( name, object ) {
  146. var fullName = object.prototype.widgetFullName || name;
  147. $.fn[ name ] = function( options ) {
  148. var isMethodCall = typeof options === "string",
  149. args = slice.call( arguments, 1 ),
  150. returnValue = this;
  151. // allow multiple hashes to be passed on init
  152. options = !isMethodCall && args.length ?
  153. $.widget.extend.apply( null, [ options ].concat(args) ) :
  154. options;
  155. if ( isMethodCall ) {
  156. this.each(function() {
  157. var methodValue,
  158. instance = $.data( this, fullName );
  159. if ( !instance ) {
  160. return $.error( "cannot call methods on " + name + " prior to initialization; " +
  161. "attempted to call method '" + options + "'" );
  162. }
  163. if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
  164. return $.error( "no such method '" + options + "' for " + name + " widget instance" );
  165. }
  166. methodValue = instance[ options ].apply( instance, args );
  167. if ( methodValue !== instance && methodValue !== undefined ) {
  168. returnValue = methodValue && methodValue.jquery ?
  169. returnValue.pushStack( methodValue.get() ) :
  170. methodValue;
  171. return false;
  172. }
  173. });
  174. } else {
  175. this.each(function() {
  176. var instance = $.data( this, fullName );
  177. if ( instance ) {
  178. instance.option( options || {} )._init();
  179. } else {
  180. $.data( this, fullName, new object( options, this ) );
  181. }
  182. });
  183. }
  184. return returnValue;
  185. };
  186. };
  187. $.Widget = function( /* options, element */ ) {};
  188. $.Widget._childConstructors = [];
  189. $.Widget.prototype = {
  190. widgetName: "widget",
  191. widgetEventPrefix: "",
  192. defaultElement: "<div>",
  193. options: {
  194. disabled: false,
  195. // callbacks
  196. create: null
  197. },
  198. _createWidget: function( options, element ) {
  199. element = $( element || this.defaultElement || this )[ 0 ];
  200. this.element = $( element );
  201. this.uuid = uuid++;
  202. this.eventNamespace = "." + this.widgetName + this.uuid;
  203. this.options = $.widget.extend( {},
  204. this.options,
  205. this._getCreateOptions(),
  206. options );
  207. this.bindings = $();
  208. this.hoverable = $();
  209. this.focusable = $();
  210. if ( element !== this ) {
  211. // 1.9 BC for #7810
  212. // TODO remove dual storage
  213. $.data( element, this.widgetName, this );
  214. $.data( element, this.widgetFullName, this );
  215. this._on( true, this.element, {
  216. remove: function( event ) {
  217. if ( event.target === element ) {
  218. this.destroy();
  219. }
  220. }
  221. });
  222. this.document = $( element.style ?
  223. // element within the document
  224. element.ownerDocument :
  225. // element is window or document
  226. element.document || element );
  227. this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
  228. }
  229. this._create();
  230. this._trigger( "create", null, this._getCreateEventData() );
  231. this._init();
  232. },
  233. _getCreateOptions: $.noop,
  234. _getCreateEventData: $.noop,
  235. _create: $.noop,
  236. _init: $.noop,
  237. destroy: function() {
  238. this._destroy();
  239. // we can probably remove the unbind calls in 2.0
  240. // all event bindings should go through this._on()
  241. this.element
  242. .unbind( this.eventNamespace )
  243. // 1.9 BC for #7810
  244. // TODO remove dual storage
  245. .removeData( this.widgetName )
  246. .removeData( this.widgetFullName )
  247. // support: jquery <1.6.3
  248. // http://bugs.jquery.com/ticket/9413
  249. .removeData( $.camelCase( this.widgetFullName ) );
  250. this.widget()
  251. .unbind( this.eventNamespace )
  252. .removeAttr( "aria-disabled" )
  253. .removeClass(
  254. this.widgetFullName + "-disabled " +
  255. "ui-state-disabled" );
  256. // clean up events and states
  257. this.bindings.unbind( this.eventNamespace );
  258. this.hoverable.removeClass( "ui-state-hover" );
  259. this.focusable.removeClass( "ui-state-focus" );
  260. },
  261. _destroy: $.noop,
  262. widget: function() {
  263. return this.element;
  264. },
  265. option: function( key, value ) {
  266. var options = key,
  267. parts,
  268. curOption,
  269. i;
  270. if ( arguments.length === 0 ) {
  271. // don't return a reference to the internal hash
  272. return $.widget.extend( {}, this.options );
  273. }
  274. if ( typeof key === "string" ) {
  275. // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
  276. options = {};
  277. parts = key.split( "." );
  278. key = parts.shift();
  279. if ( parts.length ) {
  280. curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
  281. for ( i = 0; i < parts.length - 1; i++ ) {
  282. curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
  283. curOption = curOption[ parts[ i ] ];
  284. }
  285. key = parts.pop();
  286. if ( value === undefined ) {
  287. return curOption[ key ] === undefined ? null : curOption[ key ];
  288. }
  289. curOption[ key ] = value;
  290. } else {
  291. if ( value === undefined ) {
  292. return this.options[ key ] === undefined ? null : this.options[ key ];
  293. }
  294. options[ key ] = value;
  295. }
  296. }
  297. this._setOptions( options );
  298. return this;
  299. },
  300. _setOptions: function( options ) {
  301. var key;
  302. for ( key in options ) {
  303. this._setOption( key, options[ key ] );
  304. }
  305. return this;
  306. },
  307. _setOption: function( key, value ) {
  308. this.options[ key ] = value;
  309. if ( key === "disabled" ) {
  310. this.widget()
  311. .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
  312. .attr( "aria-disabled", value );
  313. this.hoverable.removeClass( "ui-state-hover" );
  314. this.focusable.removeClass( "ui-state-focus" );
  315. }
  316. return this;
  317. },
  318. enable: function() {
  319. return this._setOption( "disabled", false );
  320. },
  321. disable: function() {
  322. return this._setOption( "disabled", true );
  323. },
  324. _on: function( suppressDisabledCheck, element, handlers ) {
  325. var delegateElement,
  326. instance = this;
  327. // no suppressDisabledCheck flag, shuffle arguments
  328. if ( typeof suppressDisabledCheck !== "boolean" ) {
  329. handlers = element;
  330. element = suppressDisabledCheck;
  331. suppressDisabledCheck = false;
  332. }
  333. // no element argument, shuffle and use this.element
  334. if ( !handlers ) {
  335. handlers = element;
  336. element = this.element;
  337. delegateElement = this.widget();
  338. } else {
  339. // accept selectors, DOM elements
  340. element = delegateElement = $( element );
  341. this.bindings = this.bindings.add( element );
  342. }
  343. $.each( handlers, function( event, handler ) {
  344. function handlerProxy() {
  345. // allow widgets to customize the disabled handling
  346. // - disabled as an array instead of boolean
  347. // - disabled class as method for disabling individual parts
  348. if ( !suppressDisabledCheck &&
  349. ( instance.options.disabled === true ||
  350. $( this ).hasClass( "ui-state-disabled" ) ) ) {
  351. return;
  352. }
  353. return ( typeof handler === "string" ? instance[ handler ] : handler )
  354. .apply( instance, arguments );
  355. }
  356. // copy the guid so direct unbinding works
  357. if ( typeof handler !== "string" ) {
  358. handlerProxy.guid = handler.guid =
  359. handler.guid || handlerProxy.guid || $.guid++;
  360. }
  361. var match = event.match( /^(\w+)\s*(.*)$/ ),
  362. eventName = match[1] + instance.eventNamespace,
  363. selector = match[2];
  364. if ( selector ) {
  365. delegateElement.delegate( selector, eventName, handlerProxy );
  366. } else {
  367. element.bind( eventName, handlerProxy );
  368. }
  369. });
  370. },
  371. _off: function( element, eventName ) {
  372. eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
  373. element.unbind( eventName ).undelegate( eventName );
  374. },
  375. _delay: function( handler, delay ) {
  376. function handlerProxy() {
  377. return ( typeof handler === "string" ? instance[ handler ] : handler )
  378. .apply( instance, arguments );
  379. }
  380. var instance = this;
  381. return setTimeout( handlerProxy, delay || 0 );
  382. },
  383. _hoverable: function( element ) {
  384. this.hoverable = this.hoverable.add( element );
  385. this._on( element, {
  386. mouseenter: function( event ) {
  387. $( event.currentTarget ).addClass( "ui-state-hover" );
  388. },
  389. mouseleave: function( event ) {
  390. $( event.currentTarget ).removeClass( "ui-state-hover" );
  391. }
  392. });
  393. },
  394. _focusable: function( element ) {
  395. this.focusable = this.focusable.add( element );
  396. this._on( element, {
  397. focusin: function( event ) {
  398. $( event.currentTarget ).addClass( "ui-state-focus" );
  399. },
  400. focusout: function( event ) {
  401. $( event.currentTarget ).removeClass( "ui-state-focus" );
  402. }
  403. });
  404. },
  405. _trigger: function( type, event, data ) {
  406. var prop, orig,
  407. callback = this.options[ type ];
  408. data = data || {};
  409. event = $.Event( event );
  410. event.type = ( type === this.widgetEventPrefix ?
  411. type :
  412. this.widgetEventPrefix + type ).toLowerCase();
  413. // the original event may come from any element
  414. // so we need to reset the target on the new event
  415. event.target = this.element[ 0 ];
  416. // copy original event properties over to the new event
  417. orig = event.originalEvent;
  418. if ( orig ) {
  419. for ( prop in orig ) {
  420. if ( !( prop in event ) ) {
  421. event[ prop ] = orig[ prop ];
  422. }
  423. }
  424. }
  425. this.element.trigger( event, data );
  426. return !( $.isFunction( callback ) &&
  427. callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
  428. event.isDefaultPrevented() );
  429. }
  430. };
  431. $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
  432. $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
  433. if ( typeof options === "string" ) {
  434. options = { effect: options };
  435. }
  436. var hasOptions,
  437. effectName = !options ?
  438. method :
  439. options === true || typeof options === "number" ?
  440. defaultEffect :
  441. options.effect || defaultEffect;
  442. options = options || {};
  443. if ( typeof options === "number" ) {
  444. options = { duration: options };
  445. }
  446. hasOptions = !$.isEmptyObject( options );
  447. options.complete = callback;
  448. if ( options.delay ) {
  449. element.delay( options.delay );
  450. }
  451. if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
  452. element[ method ]( options );
  453. } else if ( effectName !== method && element[ effectName ] ) {
  454. element[ effectName ]( options.duration, options.easing, callback );
  455. } else {
  456. element.queue(function( next ) {
  457. $( this )[ method ]();
  458. if ( callback ) {
  459. callback.call( element[ 0 ] );
  460. }
  461. next();
  462. });
  463. }
  464. };
  465. });
  466. // DEPRECATED
  467. if ( $.uiBackCompat !== false ) {
  468. $.Widget.prototype._getCreateOptions = function() {
  469. return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
  470. };
  471. }
  472. })( jQuery );