widget.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. /*!
  2. * jQuery UI Widget 1.14.1
  3. * https://jqueryui.com
  4. *
  5. * Copyright OpenJS Foundation and other contributors
  6. * Released under the MIT license.
  7. * https://jquery.org/license
  8. */
  9. //>>label: Widget
  10. //>>group: Core
  11. //>>description: Provides a factory for creating stateful widgets with a common API.
  12. //>>docs: https://api.jqueryui.com/jQuery.widget/
  13. //>>demos: https://jqueryui.com/widget/
  14. ( function( factory ) {
  15. "use strict";
  16. if ( typeof define === "function" && define.amd ) {
  17. // AMD. Register as an anonymous module.
  18. define( [ "jquery", "./version" ], factory );
  19. } else {
  20. // Browser globals
  21. factory( jQuery );
  22. }
  23. } )( function( $ ) {
  24. "use strict";
  25. var widgetUuid = 0;
  26. var widgetHasOwnProperty = Array.prototype.hasOwnProperty;
  27. var widgetSlice = Array.prototype.slice;
  28. $.cleanData = ( function( orig ) {
  29. return function( elems ) {
  30. var events, elem, i;
  31. for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
  32. // Only trigger remove when necessary to save time
  33. events = $._data( elem, "events" );
  34. if ( events && events.remove ) {
  35. $( elem ).triggerHandler( "remove" );
  36. }
  37. }
  38. orig( elems );
  39. };
  40. } )( $.cleanData );
  41. $.widget = function( name, base, prototype ) {
  42. var existingConstructor, constructor, basePrototype;
  43. // ProxiedPrototype allows the provided prototype to remain unmodified
  44. // so that it can be used as a mixin for multiple widgets (#8876)
  45. var proxiedPrototype = {};
  46. var namespace = name.split( "." )[ 0 ];
  47. name = name.split( "." )[ 1 ];
  48. if ( name === "__proto__" || name === "constructor" ) {
  49. return $.error( "Invalid widget name: " + name );
  50. }
  51. var fullName = namespace + "-" + name;
  52. if ( !prototype ) {
  53. prototype = base;
  54. base = $.Widget;
  55. }
  56. if ( Array.isArray( prototype ) ) {
  57. prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
  58. }
  59. // Create selector for plugin
  60. $.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) {
  61. return !!$.data( elem, fullName );
  62. };
  63. $[ namespace ] = $[ namespace ] || {};
  64. existingConstructor = $[ namespace ][ name ];
  65. constructor = $[ namespace ][ name ] = function( options, element ) {
  66. // Allow instantiation without "new" keyword
  67. if ( !this || !this._createWidget ) {
  68. return new constructor( options, element );
  69. }
  70. // Allow instantiation without initializing for simple inheritance
  71. // must use "new" keyword (the code above always passes args)
  72. if ( arguments.length ) {
  73. this._createWidget( options, element );
  74. }
  75. };
  76. // Extend with the existing constructor to carry over any static properties
  77. $.extend( constructor, existingConstructor, {
  78. version: prototype.version,
  79. // Copy the object used to create the prototype in case we need to
  80. // redefine the widget later
  81. _proto: $.extend( {}, prototype ),
  82. // Track widgets that inherit from this widget in case this widget is
  83. // redefined after a widget inherits from it
  84. _childConstructors: []
  85. } );
  86. basePrototype = new base();
  87. // We need to make the options hash a property directly on the new instance
  88. // otherwise we'll modify the options hash on the prototype that we're
  89. // inheriting from
  90. basePrototype.options = $.widget.extend( {}, basePrototype.options );
  91. $.each( prototype, function( prop, value ) {
  92. if ( typeof value !== "function" ) {
  93. proxiedPrototype[ prop ] = value;
  94. return;
  95. }
  96. proxiedPrototype[ prop ] = ( function() {
  97. function _super() {
  98. return base.prototype[ prop ].apply( this, arguments );
  99. }
  100. function _superApply( args ) {
  101. return base.prototype[ prop ].apply( this, args );
  102. }
  103. return function() {
  104. var __super = this._super;
  105. var __superApply = this._superApply;
  106. var returnValue;
  107. this._super = _super;
  108. this._superApply = _superApply;
  109. returnValue = value.apply( this, arguments );
  110. this._super = __super;
  111. this._superApply = __superApply;
  112. return returnValue;
  113. };
  114. } )();
  115. } );
  116. constructor.prototype = $.widget.extend( basePrototype, {
  117. // TODO: remove support for widgetEventPrefix
  118. // always use the name + a colon as the prefix, e.g., draggable:start
  119. // don't prefix for widgets that aren't DOM-based
  120. widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
  121. }, proxiedPrototype, {
  122. constructor: constructor,
  123. namespace: namespace,
  124. widgetName: name,
  125. widgetFullName: fullName
  126. } );
  127. // If this widget is being redefined then we need to find all widgets that
  128. // are inheriting from it and redefine all of them so that they inherit from
  129. // the new version of this widget. We're essentially trying to replace one
  130. // level in the prototype chain.
  131. if ( existingConstructor ) {
  132. $.each( existingConstructor._childConstructors, function( i, child ) {
  133. var childPrototype = child.prototype;
  134. // Redefine the child widget using the same prototype that was
  135. // originally used, but inherit from the new version of the base
  136. $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
  137. child._proto );
  138. } );
  139. // Remove the list of existing child constructors from the old constructor
  140. // so the old child constructors can be garbage collected
  141. delete existingConstructor._childConstructors;
  142. } else {
  143. base._childConstructors.push( constructor );
  144. }
  145. $.widget.bridge( name, constructor );
  146. return constructor;
  147. };
  148. $.widget.extend = function( target ) {
  149. var input = widgetSlice.call( arguments, 1 );
  150. var inputIndex = 0;
  151. var inputLength = input.length;
  152. var key;
  153. var value;
  154. for ( ; inputIndex < inputLength; inputIndex++ ) {
  155. for ( key in input[ inputIndex ] ) {
  156. value = input[ inputIndex ][ key ];
  157. if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) {
  158. // Clone objects
  159. if ( $.isPlainObject( value ) ) {
  160. target[ key ] = $.isPlainObject( target[ key ] ) ?
  161. $.widget.extend( {}, target[ key ], value ) :
  162. // Don't extend strings, arrays, etc. with objects
  163. $.widget.extend( {}, value );
  164. // Copy everything else by reference
  165. } else {
  166. target[ key ] = value;
  167. }
  168. }
  169. }
  170. }
  171. return target;
  172. };
  173. $.widget.bridge = function( name, object ) {
  174. var fullName = object.prototype.widgetFullName || name;
  175. $.fn[ name ] = function( options ) {
  176. var isMethodCall = typeof options === "string";
  177. var args = widgetSlice.call( arguments, 1 );
  178. var returnValue = this;
  179. if ( isMethodCall ) {
  180. // If this is an empty collection, we need to have the instance method
  181. // return undefined instead of the jQuery instance
  182. if ( !this.length && options === "instance" ) {
  183. returnValue = undefined;
  184. } else {
  185. this.each( function() {
  186. var methodValue;
  187. var instance = $.data( this, fullName );
  188. if ( options === "instance" ) {
  189. returnValue = instance;
  190. return false;
  191. }
  192. if ( !instance ) {
  193. return $.error( "cannot call methods on " + name +
  194. " prior to initialization; " +
  195. "attempted to call method '" + options + "'" );
  196. }
  197. if ( typeof instance[ options ] !== "function" ||
  198. options.charAt( 0 ) === "_" ) {
  199. return $.error( "no such method '" + options + "' for " + name +
  200. " widget instance" );
  201. }
  202. methodValue = instance[ options ].apply( instance, args );
  203. if ( methodValue !== instance && methodValue !== undefined ) {
  204. returnValue = methodValue && methodValue.jquery ?
  205. returnValue.pushStack( methodValue.get() ) :
  206. methodValue;
  207. return false;
  208. }
  209. } );
  210. }
  211. } else {
  212. // Allow multiple hashes to be passed on init
  213. if ( args.length ) {
  214. options = $.widget.extend.apply( null, [ options ].concat( args ) );
  215. }
  216. this.each( function() {
  217. var instance = $.data( this, fullName );
  218. if ( instance ) {
  219. instance.option( options || {} );
  220. if ( instance._init ) {
  221. instance._init();
  222. }
  223. } else {
  224. $.data( this, fullName, new object( options, this ) );
  225. }
  226. } );
  227. }
  228. return returnValue;
  229. };
  230. };
  231. $.Widget = function( /* options, element */ ) {};
  232. $.Widget._childConstructors = [];
  233. $.Widget.prototype = {
  234. widgetName: "widget",
  235. widgetEventPrefix: "",
  236. defaultElement: "<div>",
  237. options: {
  238. classes: {},
  239. disabled: false,
  240. // Callbacks
  241. create: null
  242. },
  243. _createWidget: function( options, element ) {
  244. element = $( element || this.defaultElement || this )[ 0 ];
  245. this.element = $( element );
  246. this.uuid = widgetUuid++;
  247. this.eventNamespace = "." + this.widgetName + this.uuid;
  248. this.bindings = $();
  249. this.hoverable = $();
  250. this.focusable = $();
  251. this.classesElementLookup = {};
  252. if ( element !== this ) {
  253. $.data( element, this.widgetFullName, this );
  254. this._on( true, this.element, {
  255. remove: function( event ) {
  256. if ( event.target === element ) {
  257. this.destroy();
  258. }
  259. }
  260. } );
  261. this.document = $( element.style ?
  262. // Element within the document
  263. element.ownerDocument :
  264. // Element is window or document
  265. element.document || element );
  266. this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
  267. }
  268. this.options = $.widget.extend( {},
  269. this.options,
  270. this._getCreateOptions(),
  271. options );
  272. this._create();
  273. if ( this.options.disabled ) {
  274. this._setOptionDisabled( this.options.disabled );
  275. }
  276. this._trigger( "create", null, this._getCreateEventData() );
  277. this._init();
  278. },
  279. _getCreateOptions: function() {
  280. return {};
  281. },
  282. _getCreateEventData: $.noop,
  283. _create: $.noop,
  284. _init: $.noop,
  285. destroy: function() {
  286. var that = this;
  287. this._destroy();
  288. $.each( this.classesElementLookup, function( key, value ) {
  289. that._removeClass( value, key );
  290. } );
  291. // We can probably remove the unbind calls in 2.0
  292. // all event bindings should go through this._on()
  293. this.element
  294. .off( this.eventNamespace )
  295. .removeData( this.widgetFullName );
  296. this.widget()
  297. .off( this.eventNamespace )
  298. .removeAttr( "aria-disabled" );
  299. // Clean up events and states
  300. this.bindings.off( this.eventNamespace );
  301. },
  302. _destroy: $.noop,
  303. widget: function() {
  304. return this.element;
  305. },
  306. option: function( key, value ) {
  307. var options = key;
  308. var parts;
  309. var curOption;
  310. var i;
  311. if ( arguments.length === 0 ) {
  312. // Don't return a reference to the internal hash
  313. return $.widget.extend( {}, this.options );
  314. }
  315. if ( typeof key === "string" ) {
  316. // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
  317. options = {};
  318. parts = key.split( "." );
  319. key = parts.shift();
  320. if ( parts.length ) {
  321. curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
  322. for ( i = 0; i < parts.length - 1; i++ ) {
  323. curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
  324. curOption = curOption[ parts[ i ] ];
  325. }
  326. key = parts.pop();
  327. if ( arguments.length === 1 ) {
  328. return curOption[ key ] === undefined ? null : curOption[ key ];
  329. }
  330. curOption[ key ] = value;
  331. } else {
  332. if ( arguments.length === 1 ) {
  333. return this.options[ key ] === undefined ? null : this.options[ key ];
  334. }
  335. options[ key ] = value;
  336. }
  337. }
  338. this._setOptions( options );
  339. return this;
  340. },
  341. _setOptions: function( options ) {
  342. var key;
  343. for ( key in options ) {
  344. this._setOption( key, options[ key ] );
  345. }
  346. return this;
  347. },
  348. _setOption: function( key, value ) {
  349. if ( key === "classes" ) {
  350. this._setOptionClasses( value );
  351. }
  352. this.options[ key ] = value;
  353. if ( key === "disabled" ) {
  354. this._setOptionDisabled( value );
  355. }
  356. return this;
  357. },
  358. _setOptionClasses: function( value ) {
  359. var classKey, elements, currentElements;
  360. for ( classKey in value ) {
  361. currentElements = this.classesElementLookup[ classKey ];
  362. if ( value[ classKey ] === this.options.classes[ classKey ] ||
  363. !currentElements ||
  364. !currentElements.length ) {
  365. continue;
  366. }
  367. // We are doing this to create a new jQuery object because the _removeClass() call
  368. // on the next line is going to destroy the reference to the current elements being
  369. // tracked. We need to save a copy of this collection so that we can add the new classes
  370. // below.
  371. elements = $( currentElements.get() );
  372. this._removeClass( currentElements, classKey );
  373. // We don't use _addClass() here, because that uses this.options.classes
  374. // for generating the string of classes. We want to use the value passed in from
  375. // _setOption(), this is the new value of the classes option which was passed to
  376. // _setOption(). We pass this value directly to _classes().
  377. elements.addClass( this._classes( {
  378. element: elements,
  379. keys: classKey,
  380. classes: value,
  381. add: true
  382. } ) );
  383. }
  384. },
  385. _setOptionDisabled: function( value ) {
  386. this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
  387. // If the widget is becoming disabled, then nothing is interactive
  388. if ( value ) {
  389. this._removeClass( this.hoverable, null, "ui-state-hover" );
  390. this._removeClass( this.focusable, null, "ui-state-focus" );
  391. }
  392. },
  393. enable: function() {
  394. return this._setOptions( { disabled: false } );
  395. },
  396. disable: function() {
  397. return this._setOptions( { disabled: true } );
  398. },
  399. _classes: function( options ) {
  400. var full = [];
  401. var that = this;
  402. options = $.extend( {
  403. element: this.element,
  404. classes: this.options.classes || {}
  405. }, options );
  406. function bindRemoveEvent() {
  407. var nodesToBind = [];
  408. options.element.each( function( _, element ) {
  409. var isTracked = $.map( that.classesElementLookup, function( elements ) {
  410. return elements;
  411. } )
  412. .some( function( elements ) {
  413. return elements.is( element );
  414. } );
  415. if ( !isTracked ) {
  416. nodesToBind.push( element );
  417. }
  418. } );
  419. that._on( $( nodesToBind ), {
  420. remove: "_untrackClassesElement"
  421. } );
  422. }
  423. function processClassString( classes, checkOption ) {
  424. var current, i;
  425. for ( i = 0; i < classes.length; i++ ) {
  426. current = that.classesElementLookup[ classes[ i ] ] || $();
  427. if ( options.add ) {
  428. bindRemoveEvent();
  429. current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) );
  430. } else {
  431. current = $( current.not( options.element ).get() );
  432. }
  433. that.classesElementLookup[ classes[ i ] ] = current;
  434. full.push( classes[ i ] );
  435. if ( checkOption && options.classes[ classes[ i ] ] ) {
  436. full.push( options.classes[ classes[ i ] ] );
  437. }
  438. }
  439. }
  440. if ( options.keys ) {
  441. processClassString( options.keys.match( /\S+/g ) || [], true );
  442. }
  443. if ( options.extra ) {
  444. processClassString( options.extra.match( /\S+/g ) || [] );
  445. }
  446. return full.join( " " );
  447. },
  448. _untrackClassesElement: function( event ) {
  449. var that = this;
  450. $.each( that.classesElementLookup, function( key, value ) {
  451. if ( $.inArray( event.target, value ) !== -1 ) {
  452. that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
  453. }
  454. } );
  455. this._off( $( event.target ) );
  456. },
  457. _removeClass: function( element, keys, extra ) {
  458. return this._toggleClass( element, keys, extra, false );
  459. },
  460. _addClass: function( element, keys, extra ) {
  461. return this._toggleClass( element, keys, extra, true );
  462. },
  463. _toggleClass: function( element, keys, extra, add ) {
  464. add = ( typeof add === "boolean" ) ? add : extra;
  465. var shift = ( typeof element === "string" || element === null ),
  466. options = {
  467. extra: shift ? keys : extra,
  468. keys: shift ? element : keys,
  469. element: shift ? this.element : element,
  470. add: add
  471. };
  472. options.element.toggleClass( this._classes( options ), add );
  473. return this;
  474. },
  475. _on: function( suppressDisabledCheck, element, handlers ) {
  476. var delegateElement;
  477. var instance = this;
  478. // No suppressDisabledCheck flag, shuffle arguments
  479. if ( typeof suppressDisabledCheck !== "boolean" ) {
  480. handlers = element;
  481. element = suppressDisabledCheck;
  482. suppressDisabledCheck = false;
  483. }
  484. // No element argument, shuffle and use this.element
  485. if ( !handlers ) {
  486. handlers = element;
  487. element = this.element;
  488. delegateElement = this.widget();
  489. } else {
  490. element = delegateElement = $( element );
  491. this.bindings = this.bindings.add( element );
  492. }
  493. $.each( handlers, function( event, handler ) {
  494. function handlerProxy() {
  495. // Allow widgets to customize the disabled handling
  496. // - disabled as an array instead of boolean
  497. // - disabled class as method for disabling individual parts
  498. if ( !suppressDisabledCheck &&
  499. ( instance.options.disabled === true ||
  500. $( this ).hasClass( "ui-state-disabled" ) ) ) {
  501. return;
  502. }
  503. return ( typeof handler === "string" ? instance[ handler ] : handler )
  504. .apply( instance, arguments );
  505. }
  506. // Copy the guid so direct unbinding works
  507. if ( typeof handler !== "string" ) {
  508. handlerProxy.guid = handler.guid =
  509. handler.guid || handlerProxy.guid || $.guid++;
  510. }
  511. var match = event.match( /^([\w:-]*)\s*(.*)$/ );
  512. var eventName = match[ 1 ] + instance.eventNamespace;
  513. var selector = match[ 2 ];
  514. if ( selector ) {
  515. delegateElement.on( eventName, selector, handlerProxy );
  516. } else {
  517. element.on( eventName, handlerProxy );
  518. }
  519. } );
  520. },
  521. _off: function( element, eventName ) {
  522. eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
  523. this.eventNamespace;
  524. element.off( eventName );
  525. // Clear the stack to avoid memory leaks (#10056)
  526. this.bindings = $( this.bindings.not( element ).get() );
  527. this.focusable = $( this.focusable.not( element ).get() );
  528. this.hoverable = $( this.hoverable.not( element ).get() );
  529. },
  530. _delay: function( handler, delay ) {
  531. function handlerProxy() {
  532. return ( typeof handler === "string" ? instance[ handler ] : handler )
  533. .apply( instance, arguments );
  534. }
  535. var instance = this;
  536. return setTimeout( handlerProxy, delay || 0 );
  537. },
  538. _hoverable: function( element ) {
  539. this.hoverable = this.hoverable.add( element );
  540. this._on( element, {
  541. mouseenter: function( event ) {
  542. this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
  543. },
  544. mouseleave: function( event ) {
  545. this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
  546. }
  547. } );
  548. },
  549. _focusable: function( element ) {
  550. this.focusable = this.focusable.add( element );
  551. this._on( element, {
  552. focusin: function( event ) {
  553. this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
  554. },
  555. focusout: function( event ) {
  556. this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
  557. }
  558. } );
  559. },
  560. _trigger: function( type, event, data ) {
  561. var prop, orig;
  562. var callback = this.options[ type ];
  563. data = data || {};
  564. event = $.Event( event );
  565. event.type = ( type === this.widgetEventPrefix ?
  566. type :
  567. this.widgetEventPrefix + type ).toLowerCase();
  568. // The original event may come from any element
  569. // so we need to reset the target on the new event
  570. event.target = this.element[ 0 ];
  571. // Copy original event properties over to the new event
  572. orig = event.originalEvent;
  573. if ( orig ) {
  574. for ( prop in orig ) {
  575. if ( !( prop in event ) ) {
  576. event[ prop ] = orig[ prop ];
  577. }
  578. }
  579. }
  580. this.element.trigger( event, data );
  581. return !( typeof callback === "function" &&
  582. callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
  583. event.isDefaultPrevented() );
  584. }
  585. };
  586. $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
  587. $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
  588. if ( typeof options === "string" ) {
  589. options = { effect: options };
  590. }
  591. var hasOptions;
  592. var effectName = !options ?
  593. method :
  594. options === true || typeof options === "number" ?
  595. defaultEffect :
  596. options.effect || defaultEffect;
  597. options = options || {};
  598. if ( typeof options === "number" ) {
  599. options = { duration: options };
  600. } else if ( options === true ) {
  601. options = {};
  602. }
  603. hasOptions = !$.isEmptyObject( options );
  604. options.complete = callback;
  605. if ( options.delay ) {
  606. element.delay( options.delay );
  607. }
  608. if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
  609. element[ method ]( options );
  610. } else if ( effectName !== method && element[ effectName ] ) {
  611. element[ effectName ]( options.duration, options.easing, callback );
  612. } else {
  613. element.queue( function( next ) {
  614. $( this )[ method ]();
  615. if ( callback ) {
  616. callback.call( element[ 0 ] );
  617. }
  618. next();
  619. } );
  620. }
  621. };
  622. } );
  623. return $.widget;
  624. } );