Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 
 

583 Zeilen
14 KiB

  1. /*!
  2. * jQuery UI Spinner 1.13.1
  3. * http://jqueryui.com
  4. *
  5. * Copyright jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. */
  9. //>>label: Spinner
  10. //>>group: Widgets
  11. //>>description: Displays buttons to easily input numbers via the keyboard or mouse.
  12. //>>docs: http://api.jqueryui.com/spinner/
  13. //>>demos: http://jqueryui.com/spinner/
  14. //>>css.structure: ../../themes/base/core.css
  15. //>>css.structure: ../../themes/base/spinner.css
  16. //>>css.theme: ../../themes/base/theme.css
  17. ( function( factory ) {
  18. "use strict";
  19. if ( typeof define === "function" && define.amd ) {
  20. // AMD. Register as an anonymous module.
  21. define( [
  22. "jquery",
  23. "./button",
  24. "../version",
  25. "../keycode",
  26. "../safe-active-element",
  27. "../widget"
  28. ], factory );
  29. } else {
  30. // Browser globals
  31. factory( jQuery );
  32. }
  33. } )( function( $ ) {
  34. "use strict";
  35. function spinnerModifier( fn ) {
  36. return function() {
  37. var previous = this.element.val();
  38. fn.apply( this, arguments );
  39. this._refresh();
  40. if ( previous !== this.element.val() ) {
  41. this._trigger( "change" );
  42. }
  43. };
  44. }
  45. $.widget( "ui.spinner", {
  46. version: "1.13.1",
  47. defaultElement: "<input>",
  48. widgetEventPrefix: "spin",
  49. options: {
  50. classes: {
  51. "ui-spinner": "ui-corner-all",
  52. "ui-spinner-down": "ui-corner-br",
  53. "ui-spinner-up": "ui-corner-tr"
  54. },
  55. culture: null,
  56. icons: {
  57. down: "ui-icon-triangle-1-s",
  58. up: "ui-icon-triangle-1-n"
  59. },
  60. incremental: true,
  61. max: null,
  62. min: null,
  63. numberFormat: null,
  64. page: 10,
  65. step: 1,
  66. change: null,
  67. spin: null,
  68. start: null,
  69. stop: null
  70. },
  71. _create: function() {
  72. // handle string values that need to be parsed
  73. this._setOption( "max", this.options.max );
  74. this._setOption( "min", this.options.min );
  75. this._setOption( "step", this.options.step );
  76. // Only format if there is a value, prevents the field from being marked
  77. // as invalid in Firefox, see #9573.
  78. if ( this.value() !== "" ) {
  79. // Format the value, but don't constrain.
  80. this._value( this.element.val(), true );
  81. }
  82. this._draw();
  83. this._on( this._events );
  84. this._refresh();
  85. // Turning off autocomplete prevents the browser from remembering the
  86. // value when navigating through history, so we re-enable autocomplete
  87. // if the page is unloaded before the widget is destroyed. #7790
  88. this._on( this.window, {
  89. beforeunload: function() {
  90. this.element.removeAttr( "autocomplete" );
  91. }
  92. } );
  93. },
  94. _getCreateOptions: function() {
  95. var options = this._super();
  96. var element = this.element;
  97. $.each( [ "min", "max", "step" ], function( i, option ) {
  98. var value = element.attr( option );
  99. if ( value != null && value.length ) {
  100. options[ option ] = value;
  101. }
  102. } );
  103. return options;
  104. },
  105. _events: {
  106. keydown: function( event ) {
  107. if ( this._start( event ) && this._keydown( event ) ) {
  108. event.preventDefault();
  109. }
  110. },
  111. keyup: "_stop",
  112. focus: function() {
  113. this.previous = this.element.val();
  114. },
  115. blur: function( event ) {
  116. if ( this.cancelBlur ) {
  117. delete this.cancelBlur;
  118. return;
  119. }
  120. this._stop();
  121. this._refresh();
  122. if ( this.previous !== this.element.val() ) {
  123. this._trigger( "change", event );
  124. }
  125. },
  126. mousewheel: function( event, delta ) {
  127. var activeElement = $.ui.safeActiveElement( this.document[ 0 ] );
  128. var isActive = this.element[ 0 ] === activeElement;
  129. if ( !isActive || !delta ) {
  130. return;
  131. }
  132. if ( !this.spinning && !this._start( event ) ) {
  133. return false;
  134. }
  135. this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event );
  136. clearTimeout( this.mousewheelTimer );
  137. this.mousewheelTimer = this._delay( function() {
  138. if ( this.spinning ) {
  139. this._stop( event );
  140. }
  141. }, 100 );
  142. event.preventDefault();
  143. },
  144. "mousedown .ui-spinner-button": function( event ) {
  145. var previous;
  146. // We never want the buttons to have focus; whenever the user is
  147. // interacting with the spinner, the focus should be on the input.
  148. // If the input is focused then this.previous is properly set from
  149. // when the input first received focus. If the input is not focused
  150. // then we need to set this.previous based on the value before spinning.
  151. previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ?
  152. this.previous : this.element.val();
  153. function checkFocus() {
  154. var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] );
  155. if ( !isActive ) {
  156. this.element.trigger( "focus" );
  157. this.previous = previous;
  158. // support: IE
  159. // IE sets focus asynchronously, so we need to check if focus
  160. // moved off of the input because the user clicked on the button.
  161. this._delay( function() {
  162. this.previous = previous;
  163. } );
  164. }
  165. }
  166. // Ensure focus is on (or stays on) the text field
  167. event.preventDefault();
  168. checkFocus.call( this );
  169. // Support: IE
  170. // IE doesn't prevent moving focus even with event.preventDefault()
  171. // so we set a flag to know when we should ignore the blur event
  172. // and check (again) if focus moved off of the input.
  173. this.cancelBlur = true;
  174. this._delay( function() {
  175. delete this.cancelBlur;
  176. checkFocus.call( this );
  177. } );
  178. if ( this._start( event ) === false ) {
  179. return;
  180. }
  181. this._repeat( null, $( event.currentTarget )
  182. .hasClass( "ui-spinner-up" ) ? 1 : -1, event );
  183. },
  184. "mouseup .ui-spinner-button": "_stop",
  185. "mouseenter .ui-spinner-button": function( event ) {
  186. // button will add ui-state-active if mouse was down while mouseleave and kept down
  187. if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
  188. return;
  189. }
  190. if ( this._start( event ) === false ) {
  191. return false;
  192. }
  193. this._repeat( null, $( event.currentTarget )
  194. .hasClass( "ui-spinner-up" ) ? 1 : -1, event );
  195. },
  196. // TODO: do we really want to consider this a stop?
  197. // shouldn't we just stop the repeater and wait until mouseup before
  198. // we trigger the stop event?
  199. "mouseleave .ui-spinner-button": "_stop"
  200. },
  201. // Support mobile enhanced option and make backcompat more sane
  202. _enhance: function() {
  203. this.uiSpinner = this.element
  204. .attr( "autocomplete", "off" )
  205. .wrap( "<span>" )
  206. .parent()
  207. // Add buttons
  208. .append(
  209. "<a></a><a></a>"
  210. );
  211. },
  212. _draw: function() {
  213. this._enhance();
  214. this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" );
  215. this._addClass( "ui-spinner-input" );
  216. this.element.attr( "role", "spinbutton" );
  217. // Button bindings
  218. this.buttons = this.uiSpinner.children( "a" )
  219. .attr( "tabIndex", -1 )
  220. .attr( "aria-hidden", true )
  221. .button( {
  222. classes: {
  223. "ui-button": ""
  224. }
  225. } );
  226. // TODO: Right now button does not support classes this is already updated in button PR
  227. this._removeClass( this.buttons, "ui-corner-all" );
  228. this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" );
  229. this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" );
  230. this.buttons.first().button( {
  231. "icon": this.options.icons.up,
  232. "showLabel": false
  233. } );
  234. this.buttons.last().button( {
  235. "icon": this.options.icons.down,
  236. "showLabel": false
  237. } );
  238. // IE 6 doesn't understand height: 50% for the buttons
  239. // unless the wrapper has an explicit height
  240. if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) &&
  241. this.uiSpinner.height() > 0 ) {
  242. this.uiSpinner.height( this.uiSpinner.height() );
  243. }
  244. },
  245. _keydown: function( event ) {
  246. var options = this.options,
  247. keyCode = $.ui.keyCode;
  248. switch ( event.keyCode ) {
  249. case keyCode.UP:
  250. this._repeat( null, 1, event );
  251. return true;
  252. case keyCode.DOWN:
  253. this._repeat( null, -1, event );
  254. return true;
  255. case keyCode.PAGE_UP:
  256. this._repeat( null, options.page, event );
  257. return true;
  258. case keyCode.PAGE_DOWN:
  259. this._repeat( null, -options.page, event );
  260. return true;
  261. }
  262. return false;
  263. },
  264. _start: function( event ) {
  265. if ( !this.spinning && this._trigger( "start", event ) === false ) {
  266. return false;
  267. }
  268. if ( !this.counter ) {
  269. this.counter = 1;
  270. }
  271. this.spinning = true;
  272. return true;
  273. },
  274. _repeat: function( i, steps, event ) {
  275. i = i || 500;
  276. clearTimeout( this.timer );
  277. this.timer = this._delay( function() {
  278. this._repeat( 40, steps, event );
  279. }, i );
  280. this._spin( steps * this.options.step, event );
  281. },
  282. _spin: function( step, event ) {
  283. var value = this.value() || 0;
  284. if ( !this.counter ) {
  285. this.counter = 1;
  286. }
  287. value = this._adjustValue( value + step * this._increment( this.counter ) );
  288. if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) {
  289. this._value( value );
  290. this.counter++;
  291. }
  292. },
  293. _increment: function( i ) {
  294. var incremental = this.options.incremental;
  295. if ( incremental ) {
  296. return typeof incremental === "function" ?
  297. incremental( i ) :
  298. Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );
  299. }
  300. return 1;
  301. },
  302. _precision: function() {
  303. var precision = this._precisionOf( this.options.step );
  304. if ( this.options.min !== null ) {
  305. precision = Math.max( precision, this._precisionOf( this.options.min ) );
  306. }
  307. return precision;
  308. },
  309. _precisionOf: function( num ) {
  310. var str = num.toString(),
  311. decimal = str.indexOf( "." );
  312. return decimal === -1 ? 0 : str.length - decimal - 1;
  313. },
  314. _adjustValue: function( value ) {
  315. var base, aboveMin,
  316. options = this.options;
  317. // Make sure we're at a valid step
  318. // - find out where we are relative to the base (min or 0)
  319. base = options.min !== null ? options.min : 0;
  320. aboveMin = value - base;
  321. // - round to the nearest step
  322. aboveMin = Math.round( aboveMin / options.step ) * options.step;
  323. // - rounding is based on 0, so adjust back to our base
  324. value = base + aboveMin;
  325. // Fix precision from bad JS floating point math
  326. value = parseFloat( value.toFixed( this._precision() ) );
  327. // Clamp the value
  328. if ( options.max !== null && value > options.max ) {
  329. return options.max;
  330. }
  331. if ( options.min !== null && value < options.min ) {
  332. return options.min;
  333. }
  334. return value;
  335. },
  336. _stop: function( event ) {
  337. if ( !this.spinning ) {
  338. return;
  339. }
  340. clearTimeout( this.timer );
  341. clearTimeout( this.mousewheelTimer );
  342. this.counter = 0;
  343. this.spinning = false;
  344. this._trigger( "stop", event );
  345. },
  346. _setOption: function( key, value ) {
  347. var prevValue, first, last;
  348. if ( key === "culture" || key === "numberFormat" ) {
  349. prevValue = this._parse( this.element.val() );
  350. this.options[ key ] = value;
  351. this.element.val( this._format( prevValue ) );
  352. return;
  353. }
  354. if ( key === "max" || key === "min" || key === "step" ) {
  355. if ( typeof value === "string" ) {
  356. value = this._parse( value );
  357. }
  358. }
  359. if ( key === "icons" ) {
  360. first = this.buttons.first().find( ".ui-icon" );
  361. this._removeClass( first, null, this.options.icons.up );
  362. this._addClass( first, null, value.up );
  363. last = this.buttons.last().find( ".ui-icon" );
  364. this._removeClass( last, null, this.options.icons.down );
  365. this._addClass( last, null, value.down );
  366. }
  367. this._super( key, value );
  368. },
  369. _setOptionDisabled: function( value ) {
  370. this._super( value );
  371. this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value );
  372. this.element.prop( "disabled", !!value );
  373. this.buttons.button( value ? "disable" : "enable" );
  374. },
  375. _setOptions: spinnerModifier( function( options ) {
  376. this._super( options );
  377. } ),
  378. _parse: function( val ) {
  379. if ( typeof val === "string" && val !== "" ) {
  380. val = window.Globalize && this.options.numberFormat ?
  381. Globalize.parseFloat( val, 10, this.options.culture ) : +val;
  382. }
  383. return val === "" || isNaN( val ) ? null : val;
  384. },
  385. _format: function( value ) {
  386. if ( value === "" ) {
  387. return "";
  388. }
  389. return window.Globalize && this.options.numberFormat ?
  390. Globalize.format( value, this.options.numberFormat, this.options.culture ) :
  391. value;
  392. },
  393. _refresh: function() {
  394. this.element.attr( {
  395. "aria-valuemin": this.options.min,
  396. "aria-valuemax": this.options.max,
  397. // TODO: what should we do with values that can't be parsed?
  398. "aria-valuenow": this._parse( this.element.val() )
  399. } );
  400. },
  401. isValid: function() {
  402. var value = this.value();
  403. // Null is invalid
  404. if ( value === null ) {
  405. return false;
  406. }
  407. // If value gets adjusted, it's invalid
  408. return value === this._adjustValue( value );
  409. },
  410. // Update the value without triggering change
  411. _value: function( value, allowAny ) {
  412. var parsed;
  413. if ( value !== "" ) {
  414. parsed = this._parse( value );
  415. if ( parsed !== null ) {
  416. if ( !allowAny ) {
  417. parsed = this._adjustValue( parsed );
  418. }
  419. value = this._format( parsed );
  420. }
  421. }
  422. this.element.val( value );
  423. this._refresh();
  424. },
  425. _destroy: function() {
  426. this.element
  427. .prop( "disabled", false )
  428. .removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" );
  429. this.uiSpinner.replaceWith( this.element );
  430. },
  431. stepUp: spinnerModifier( function( steps ) {
  432. this._stepUp( steps );
  433. } ),
  434. _stepUp: function( steps ) {
  435. if ( this._start() ) {
  436. this._spin( ( steps || 1 ) * this.options.step );
  437. this._stop();
  438. }
  439. },
  440. stepDown: spinnerModifier( function( steps ) {
  441. this._stepDown( steps );
  442. } ),
  443. _stepDown: function( steps ) {
  444. if ( this._start() ) {
  445. this._spin( ( steps || 1 ) * -this.options.step );
  446. this._stop();
  447. }
  448. },
  449. pageUp: spinnerModifier( function( pages ) {
  450. this._stepUp( ( pages || 1 ) * this.options.page );
  451. } ),
  452. pageDown: spinnerModifier( function( pages ) {
  453. this._stepDown( ( pages || 1 ) * this.options.page );
  454. } ),
  455. value: function( newVal ) {
  456. if ( !arguments.length ) {
  457. return this._parse( this.element.val() );
  458. }
  459. spinnerModifier( this._value ).call( this, newVal );
  460. },
  461. widget: function() {
  462. return this.uiSpinner;
  463. }
  464. } );
  465. // DEPRECATED
  466. // TODO: switch return back to widget declaration at top of file when this is removed
  467. if ( $.uiBackCompat !== false ) {
  468. // Backcompat for spinner html extension points
  469. $.widget( "ui.spinner", $.ui.spinner, {
  470. _enhance: function() {
  471. this.uiSpinner = this.element
  472. .attr( "autocomplete", "off" )
  473. .wrap( this._uiSpinnerHtml() )
  474. .parent()
  475. // Add buttons
  476. .append( this._buttonHtml() );
  477. },
  478. _uiSpinnerHtml: function() {
  479. return "<span>";
  480. },
  481. _buttonHtml: function() {
  482. return "<a></a><a></a>";
  483. }
  484. } );
  485. }
  486. return $.ui.spinner;
  487. } );