slider.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. /*!
  2. * jQuery UI Slider 1.11.4
  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. * http://api.jqueryui.com/slider/
  10. */
  11. (function( factory ) {
  12. if ( typeof define === "function" && define.amd ) {
  13. // AMD. Register as an anonymous module.
  14. define([
  15. "jquery",
  16. "./core",
  17. "./mouse",
  18. "./widget"
  19. ], factory );
  20. } else {
  21. // Browser globals
  22. factory( jQuery );
  23. }
  24. }(function( $ ) {
  25. return $.widget( "ui.slider", $.ui.mouse, {
  26. version: "1.11.4",
  27. widgetEventPrefix: "slide",
  28. options: {
  29. animate: false,
  30. distance: 0,
  31. max: 100,
  32. min: 0,
  33. orientation: "horizontal",
  34. range: false,
  35. step: 1,
  36. value: 0,
  37. values: null,
  38. // callbacks
  39. change: null,
  40. slide: null,
  41. start: null,
  42. stop: null
  43. },
  44. // number of pages in a slider
  45. // (how many times can you page up/down to go through the whole range)
  46. numPages: 5,
  47. _create: function() {
  48. this._keySliding = false;
  49. this._mouseSliding = false;
  50. this._animateOff = true;
  51. this._handleIndex = null;
  52. this._detectOrientation();
  53. this._mouseInit();
  54. this._calculateNewMax();
  55. this.element
  56. .addClass( "ui-slider" +
  57. " ui-slider-" + this.orientation +
  58. " ui-widget" +
  59. " ui-widget-content" +
  60. " ui-corner-all");
  61. this._refresh();
  62. this._setOption( "disabled", this.options.disabled );
  63. this._animateOff = false;
  64. },
  65. _refresh: function() {
  66. this._createRange();
  67. this._createHandles();
  68. this._setupEvents();
  69. this._refreshValue();
  70. },
  71. _createHandles: function() {
  72. var i, handleCount,
  73. options = this.options,
  74. existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
  75. handle = "<span class='ui-slider-handle ui-state-default ui-corner-all' tabindex='0'></span>",
  76. handles = [];
  77. handleCount = ( options.values && options.values.length ) || 1;
  78. if ( existingHandles.length > handleCount ) {
  79. existingHandles.slice( handleCount ).remove();
  80. existingHandles = existingHandles.slice( 0, handleCount );
  81. }
  82. for ( i = existingHandles.length; i < handleCount; i++ ) {
  83. handles.push( handle );
  84. }
  85. this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
  86. this.handle = this.handles.eq( 0 );
  87. this.handles.each(function( i ) {
  88. $( this ).data( "ui-slider-handle-index", i );
  89. });
  90. },
  91. _createRange: function() {
  92. var options = this.options,
  93. classes = "";
  94. if ( options.range ) {
  95. if ( options.range === true ) {
  96. if ( !options.values ) {
  97. options.values = [ this._valueMin(), this._valueMin() ];
  98. } else if ( options.values.length && options.values.length !== 2 ) {
  99. options.values = [ options.values[0], options.values[0] ];
  100. } else if ( $.isArray( options.values ) ) {
  101. options.values = options.values.slice(0);
  102. }
  103. }
  104. if ( !this.range || !this.range.length ) {
  105. this.range = $( "<div></div>" )
  106. .appendTo( this.element );
  107. classes = "ui-slider-range" +
  108. // note: this isn't the most fittingly semantic framework class for this element,
  109. // but worked best visually with a variety of themes
  110. " ui-widget-header ui-corner-all";
  111. } else {
  112. this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
  113. // Handle range switching from true to min/max
  114. .css({
  115. "left": "",
  116. "bottom": ""
  117. });
  118. }
  119. this.range.addClass( classes +
  120. ( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
  121. } else {
  122. if ( this.range ) {
  123. this.range.remove();
  124. }
  125. this.range = null;
  126. }
  127. },
  128. _setupEvents: function() {
  129. this._off( this.handles );
  130. this._on( this.handles, this._handleEvents );
  131. this._hoverable( this.handles );
  132. this._focusable( this.handles );
  133. },
  134. _destroy: function() {
  135. this.handles.remove();
  136. if ( this.range ) {
  137. this.range.remove();
  138. }
  139. this.element
  140. .removeClass( "ui-slider" +
  141. " ui-slider-horizontal" +
  142. " ui-slider-vertical" +
  143. " ui-widget" +
  144. " ui-widget-content" +
  145. " ui-corner-all" );
  146. this._mouseDestroy();
  147. },
  148. _mouseCapture: function( event ) {
  149. var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
  150. that = this,
  151. o = this.options;
  152. if ( o.disabled ) {
  153. return false;
  154. }
  155. this.elementSize = {
  156. width: this.element.outerWidth(),
  157. height: this.element.outerHeight()
  158. };
  159. this.elementOffset = this.element.offset();
  160. position = { x: event.pageX, y: event.pageY };
  161. normValue = this._normValueFromMouse( position );
  162. distance = this._valueMax() - this._valueMin() + 1;
  163. this.handles.each(function( i ) {
  164. var thisDistance = Math.abs( normValue - that.values(i) );
  165. if (( distance > thisDistance ) ||
  166. ( distance === thisDistance &&
  167. (i === that._lastChangedValue || that.values(i) === o.min ))) {
  168. distance = thisDistance;
  169. closestHandle = $( this );
  170. index = i;
  171. }
  172. });
  173. allowed = this._start( event, index );
  174. if ( allowed === false ) {
  175. return false;
  176. }
  177. this._mouseSliding = true;
  178. this._handleIndex = index;
  179. closestHandle
  180. .addClass( "ui-state-active" )
  181. .focus();
  182. offset = closestHandle.offset();
  183. mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
  184. this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
  185. left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
  186. top: event.pageY - offset.top -
  187. ( closestHandle.height() / 2 ) -
  188. ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
  189. ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
  190. ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
  191. };
  192. if ( !this.handles.hasClass( "ui-state-hover" ) ) {
  193. this._slide( event, index, normValue );
  194. }
  195. this._animateOff = true;
  196. return true;
  197. },
  198. _mouseStart: function() {
  199. return true;
  200. },
  201. _mouseDrag: function( event ) {
  202. var position = { x: event.pageX, y: event.pageY },
  203. normValue = this._normValueFromMouse( position );
  204. this._slide( event, this._handleIndex, normValue );
  205. return false;
  206. },
  207. _mouseStop: function( event ) {
  208. this.handles.removeClass( "ui-state-active" );
  209. this._mouseSliding = false;
  210. this._stop( event, this._handleIndex );
  211. this._change( event, this._handleIndex );
  212. this._handleIndex = null;
  213. this._clickOffset = null;
  214. this._animateOff = false;
  215. return false;
  216. },
  217. _detectOrientation: function() {
  218. this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
  219. },
  220. _normValueFromMouse: function( position ) {
  221. var pixelTotal,
  222. pixelMouse,
  223. percentMouse,
  224. valueTotal,
  225. valueMouse;
  226. if ( this.orientation === "horizontal" ) {
  227. pixelTotal = this.elementSize.width;
  228. pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
  229. } else {
  230. pixelTotal = this.elementSize.height;
  231. pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
  232. }
  233. percentMouse = ( pixelMouse / pixelTotal );
  234. if ( percentMouse > 1 ) {
  235. percentMouse = 1;
  236. }
  237. if ( percentMouse < 0 ) {
  238. percentMouse = 0;
  239. }
  240. if ( this.orientation === "vertical" ) {
  241. percentMouse = 1 - percentMouse;
  242. }
  243. valueTotal = this._valueMax() - this._valueMin();
  244. valueMouse = this._valueMin() + percentMouse * valueTotal;
  245. return this._trimAlignValue( valueMouse );
  246. },
  247. _start: function( event, index ) {
  248. var uiHash = {
  249. handle: this.handles[ index ],
  250. value: this.value()
  251. };
  252. if ( this.options.values && this.options.values.length ) {
  253. uiHash.value = this.values( index );
  254. uiHash.values = this.values();
  255. }
  256. return this._trigger( "start", event, uiHash );
  257. },
  258. _slide: function( event, index, newVal ) {
  259. var otherVal,
  260. newValues,
  261. allowed;
  262. if ( this.options.values && this.options.values.length ) {
  263. otherVal = this.values( index ? 0 : 1 );
  264. if ( ( this.options.values.length === 2 && this.options.range === true ) &&
  265. ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
  266. ) {
  267. newVal = otherVal;
  268. }
  269. if ( newVal !== this.values( index ) ) {
  270. newValues = this.values();
  271. newValues[ index ] = newVal;
  272. // A slide can be canceled by returning false from the slide callback
  273. allowed = this._trigger( "slide", event, {
  274. handle: this.handles[ index ],
  275. value: newVal,
  276. values: newValues
  277. } );
  278. otherVal = this.values( index ? 0 : 1 );
  279. if ( allowed !== false ) {
  280. this.values( index, newVal );
  281. }
  282. }
  283. } else {
  284. if ( newVal !== this.value() ) {
  285. // A slide can be canceled by returning false from the slide callback
  286. allowed = this._trigger( "slide", event, {
  287. handle: this.handles[ index ],
  288. value: newVal
  289. } );
  290. if ( allowed !== false ) {
  291. this.value( newVal );
  292. }
  293. }
  294. }
  295. },
  296. _stop: function( event, index ) {
  297. var uiHash = {
  298. handle: this.handles[ index ],
  299. value: this.value()
  300. };
  301. if ( this.options.values && this.options.values.length ) {
  302. uiHash.value = this.values( index );
  303. uiHash.values = this.values();
  304. }
  305. this._trigger( "stop", event, uiHash );
  306. },
  307. _change: function( event, index ) {
  308. if ( !this._keySliding && !this._mouseSliding ) {
  309. var uiHash = {
  310. handle: this.handles[ index ],
  311. value: this.value()
  312. };
  313. if ( this.options.values && this.options.values.length ) {
  314. uiHash.value = this.values( index );
  315. uiHash.values = this.values();
  316. }
  317. //store the last changed value index for reference when handles overlap
  318. this._lastChangedValue = index;
  319. this._trigger( "change", event, uiHash );
  320. }
  321. },
  322. value: function( newValue ) {
  323. if ( arguments.length ) {
  324. this.options.value = this._trimAlignValue( newValue );
  325. this._refreshValue();
  326. this._change( null, 0 );
  327. return;
  328. }
  329. return this._value();
  330. },
  331. values: function( index, newValue ) {
  332. var vals,
  333. newValues,
  334. i;
  335. if ( arguments.length > 1 ) {
  336. this.options.values[ index ] = this._trimAlignValue( newValue );
  337. this._refreshValue();
  338. this._change( null, index );
  339. return;
  340. }
  341. if ( arguments.length ) {
  342. if ( $.isArray( arguments[ 0 ] ) ) {
  343. vals = this.options.values;
  344. newValues = arguments[ 0 ];
  345. for ( i = 0; i < vals.length; i += 1 ) {
  346. vals[ i ] = this._trimAlignValue( newValues[ i ] );
  347. this._change( null, i );
  348. }
  349. this._refreshValue();
  350. } else {
  351. if ( this.options.values && this.options.values.length ) {
  352. return this._values( index );
  353. } else {
  354. return this.value();
  355. }
  356. }
  357. } else {
  358. return this._values();
  359. }
  360. },
  361. _setOption: function( key, value ) {
  362. var i,
  363. valsLength = 0;
  364. if ( key === "range" && this.options.range === true ) {
  365. if ( value === "min" ) {
  366. this.options.value = this._values( 0 );
  367. this.options.values = null;
  368. } else if ( value === "max" ) {
  369. this.options.value = this._values( this.options.values.length - 1 );
  370. this.options.values = null;
  371. }
  372. }
  373. if ( $.isArray( this.options.values ) ) {
  374. valsLength = this.options.values.length;
  375. }
  376. if ( key === "disabled" ) {
  377. this.element.toggleClass( "ui-state-disabled", !!value );
  378. }
  379. this._super( key, value );
  380. switch ( key ) {
  381. case "orientation":
  382. this._detectOrientation();
  383. this.element
  384. .removeClass( "ui-slider-horizontal ui-slider-vertical" )
  385. .addClass( "ui-slider-" + this.orientation );
  386. this._refreshValue();
  387. // Reset positioning from previous orientation
  388. this.handles.css( value === "horizontal" ? "bottom" : "left", "" );
  389. break;
  390. case "value":
  391. this._animateOff = true;
  392. this._refreshValue();
  393. this._change( null, 0 );
  394. this._animateOff = false;
  395. break;
  396. case "values":
  397. this._animateOff = true;
  398. this._refreshValue();
  399. for ( i = 0; i < valsLength; i += 1 ) {
  400. this._change( null, i );
  401. }
  402. this._animateOff = false;
  403. break;
  404. case "step":
  405. case "min":
  406. case "max":
  407. this._animateOff = true;
  408. this._calculateNewMax();
  409. this._refreshValue();
  410. this._animateOff = false;
  411. break;
  412. case "range":
  413. this._animateOff = true;
  414. this._refresh();
  415. this._animateOff = false;
  416. break;
  417. }
  418. },
  419. //internal value getter
  420. // _value() returns value trimmed by min and max, aligned by step
  421. _value: function() {
  422. var val = this.options.value;
  423. val = this._trimAlignValue( val );
  424. return val;
  425. },
  426. //internal values getter
  427. // _values() returns array of values trimmed by min and max, aligned by step
  428. // _values( index ) returns single value trimmed by min and max, aligned by step
  429. _values: function( index ) {
  430. var val,
  431. vals,
  432. i;
  433. if ( arguments.length ) {
  434. val = this.options.values[ index ];
  435. val = this._trimAlignValue( val );
  436. return val;
  437. } else if ( this.options.values && this.options.values.length ) {
  438. // .slice() creates a copy of the array
  439. // this copy gets trimmed by min and max and then returned
  440. vals = this.options.values.slice();
  441. for ( i = 0; i < vals.length; i += 1) {
  442. vals[ i ] = this._trimAlignValue( vals[ i ] );
  443. }
  444. return vals;
  445. } else {
  446. return [];
  447. }
  448. },
  449. // returns the step-aligned value that val is closest to, between (inclusive) min and max
  450. _trimAlignValue: function( val ) {
  451. if ( val <= this._valueMin() ) {
  452. return this._valueMin();
  453. }
  454. if ( val >= this._valueMax() ) {
  455. return this._valueMax();
  456. }
  457. var step = ( this.options.step > 0 ) ? this.options.step : 1,
  458. valModStep = (val - this._valueMin()) % step,
  459. alignValue = val - valModStep;
  460. if ( Math.abs(valModStep) * 2 >= step ) {
  461. alignValue += ( valModStep > 0 ) ? step : ( -step );
  462. }
  463. // Since JavaScript has problems with large floats, round
  464. // the final value to 5 digits after the decimal point (see #4124)
  465. return parseFloat( alignValue.toFixed(5) );
  466. },
  467. _calculateNewMax: function() {
  468. var max = this.options.max,
  469. min = this._valueMin(),
  470. step = this.options.step,
  471. aboveMin = Math.floor( ( +( max - min ).toFixed( this._precision() ) ) / step ) * step;
  472. max = aboveMin + min;
  473. this.max = parseFloat( max.toFixed( this._precision() ) );
  474. },
  475. _precision: function() {
  476. var precision = this._precisionOf( this.options.step );
  477. if ( this.options.min !== null ) {
  478. precision = Math.max( precision, this._precisionOf( this.options.min ) );
  479. }
  480. return precision;
  481. },
  482. _precisionOf: function( num ) {
  483. var str = num.toString(),
  484. decimal = str.indexOf( "." );
  485. return decimal === -1 ? 0 : str.length - decimal - 1;
  486. },
  487. _valueMin: function() {
  488. return this.options.min;
  489. },
  490. _valueMax: function() {
  491. return this.max;
  492. },
  493. _refreshValue: function() {
  494. var lastValPercent, valPercent, value, valueMin, valueMax,
  495. oRange = this.options.range,
  496. o = this.options,
  497. that = this,
  498. animate = ( !this._animateOff ) ? o.animate : false,
  499. _set = {};
  500. if ( this.options.values && this.options.values.length ) {
  501. this.handles.each(function( i ) {
  502. valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
  503. _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
  504. $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
  505. if ( that.options.range === true ) {
  506. if ( that.orientation === "horizontal" ) {
  507. if ( i === 0 ) {
  508. that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
  509. }
  510. if ( i === 1 ) {
  511. that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
  512. }
  513. } else {
  514. if ( i === 0 ) {
  515. that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
  516. }
  517. if ( i === 1 ) {
  518. that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
  519. }
  520. }
  521. }
  522. lastValPercent = valPercent;
  523. });
  524. } else {
  525. value = this.value();
  526. valueMin = this._valueMin();
  527. valueMax = this._valueMax();
  528. valPercent = ( valueMax !== valueMin ) ?
  529. ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
  530. 0;
  531. _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
  532. this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
  533. if ( oRange === "min" && this.orientation === "horizontal" ) {
  534. this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
  535. }
  536. if ( oRange === "max" && this.orientation === "horizontal" ) {
  537. this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
  538. }
  539. if ( oRange === "min" && this.orientation === "vertical" ) {
  540. this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
  541. }
  542. if ( oRange === "max" && this.orientation === "vertical" ) {
  543. this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
  544. }
  545. }
  546. },
  547. _handleEvents: {
  548. keydown: function( event ) {
  549. var allowed, curVal, newVal, step,
  550. index = $( event.target ).data( "ui-slider-handle-index" );
  551. switch ( event.keyCode ) {
  552. case $.ui.keyCode.HOME:
  553. case $.ui.keyCode.END:
  554. case $.ui.keyCode.PAGE_UP:
  555. case $.ui.keyCode.PAGE_DOWN:
  556. case $.ui.keyCode.UP:
  557. case $.ui.keyCode.RIGHT:
  558. case $.ui.keyCode.DOWN:
  559. case $.ui.keyCode.LEFT:
  560. event.preventDefault();
  561. if ( !this._keySliding ) {
  562. this._keySliding = true;
  563. $( event.target ).addClass( "ui-state-active" );
  564. allowed = this._start( event, index );
  565. if ( allowed === false ) {
  566. return;
  567. }
  568. }
  569. break;
  570. }
  571. step = this.options.step;
  572. if ( this.options.values && this.options.values.length ) {
  573. curVal = newVal = this.values( index );
  574. } else {
  575. curVal = newVal = this.value();
  576. }
  577. switch ( event.keyCode ) {
  578. case $.ui.keyCode.HOME:
  579. newVal = this._valueMin();
  580. break;
  581. case $.ui.keyCode.END:
  582. newVal = this._valueMax();
  583. break;
  584. case $.ui.keyCode.PAGE_UP:
  585. newVal = this._trimAlignValue(
  586. curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )
  587. );
  588. break;
  589. case $.ui.keyCode.PAGE_DOWN:
  590. newVal = this._trimAlignValue(
  591. curVal - ( (this._valueMax() - this._valueMin()) / this.numPages ) );
  592. break;
  593. case $.ui.keyCode.UP:
  594. case $.ui.keyCode.RIGHT:
  595. if ( curVal === this._valueMax() ) {
  596. return;
  597. }
  598. newVal = this._trimAlignValue( curVal + step );
  599. break;
  600. case $.ui.keyCode.DOWN:
  601. case $.ui.keyCode.LEFT:
  602. if ( curVal === this._valueMin() ) {
  603. return;
  604. }
  605. newVal = this._trimAlignValue( curVal - step );
  606. break;
  607. }
  608. this._slide( event, index, newVal );
  609. },
  610. keyup: function( event ) {
  611. var index = $( event.target ).data( "ui-slider-handle-index" );
  612. if ( this._keySliding ) {
  613. this._keySliding = false;
  614. this._stop( event, index );
  615. this._change( event, index );
  616. $( event.target ).removeClass( "ui-state-active" );
  617. }
  618. }
  619. }
  620. });
  621. }));