1 /*! jQuery UI - v1.9.2 - 2015-05-28
3 * Includes: jquery.ui.slider.js
4 * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
7 // requires: core, widget, mouse, position
9 (function( $, undefined ) {
11 // number of pages in a slider
12 // (how many times can you page up/down to go through the whole range)
15 $.widget( "ui.slider", $.ui.mouse, {
17 widgetEventPrefix: "slide",
24 orientation: "horizontal",
34 existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
35 handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
38 this._keySliding = false;
39 this._mouseSliding = false;
40 this._animateOff = true;
41 this._handleIndex = null;
42 this._detectOrientation();
46 .addClass( "ui-slider" +
47 " ui-slider-" + this.orientation +
49 " ui-widget-content" +
51 ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
56 if ( o.range === true ) {
58 o.values = [ this._valueMin(), this._valueMin() ];
60 if ( o.values.length && o.values.length !== 2 ) {
61 o.values = [ o.values[0], o.values[0] ];
65 this.range = $( "<div></div>" )
66 .appendTo( this.element )
67 .addClass( "ui-slider-range" +
68 // note: this isn't the most fittingly semantic framework class for this element,
69 // but worked best visually with a variety of themes
71 ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
74 handleCount = ( o.values && o.values.length ) || 1;
76 for ( i = existingHandles.length; i < handleCount; i++ ) {
77 handles.push( handle );
80 this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
82 this.handle = this.handles.eq( 0 );
84 this.handles.add( this.range ).filter( "a" )
85 .click(function( event ) {
86 event.preventDefault();
88 .mouseenter(function() {
90 $( this ).addClass( "ui-state-hover" );
93 .mouseleave(function() {
94 $( this ).removeClass( "ui-state-hover" );
98 $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
99 $( this ).addClass( "ui-state-focus" );
105 $( this ).removeClass( "ui-state-focus" );
108 this.handles.each(function( i ) {
109 $( this ).data( "ui-slider-handle-index", i );
112 this._on( this.handles, {
113 keydown: function( event ) {
114 var allowed, curVal, newVal, step,
115 index = $( event.target ).data( "ui-slider-handle-index" );
117 switch ( event.keyCode ) {
118 case $.ui.keyCode.HOME:
119 case $.ui.keyCode.END:
120 case $.ui.keyCode.PAGE_UP:
121 case $.ui.keyCode.PAGE_DOWN:
122 case $.ui.keyCode.UP:
123 case $.ui.keyCode.RIGHT:
124 case $.ui.keyCode.DOWN:
125 case $.ui.keyCode.LEFT:
126 event.preventDefault();
127 if ( !this._keySliding ) {
128 this._keySliding = true;
129 $( event.target ).addClass( "ui-state-active" );
130 allowed = this._start( event, index );
131 if ( allowed === false ) {
138 step = this.options.step;
139 if ( this.options.values && this.options.values.length ) {
140 curVal = newVal = this.values( index );
142 curVal = newVal = this.value();
145 switch ( event.keyCode ) {
146 case $.ui.keyCode.HOME:
147 newVal = this._valueMin();
149 case $.ui.keyCode.END:
150 newVal = this._valueMax();
152 case $.ui.keyCode.PAGE_UP:
153 newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
155 case $.ui.keyCode.PAGE_DOWN:
156 newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
158 case $.ui.keyCode.UP:
159 case $.ui.keyCode.RIGHT:
160 if ( curVal === this._valueMax() ) {
163 newVal = this._trimAlignValue( curVal + step );
165 case $.ui.keyCode.DOWN:
166 case $.ui.keyCode.LEFT:
167 if ( curVal === this._valueMin() ) {
170 newVal = this._trimAlignValue( curVal - step );
174 this._slide( event, index, newVal );
176 keyup: function( event ) {
177 var index = $( event.target ).data( "ui-slider-handle-index" );
179 if ( this._keySliding ) {
180 this._keySliding = false;
181 this._stop( event, index );
182 this._change( event, index );
183 $( event.target ).removeClass( "ui-state-active" );
188 this._refreshValue();
190 this._animateOff = false;
193 _destroy: function() {
194 this.handles.remove();
198 .removeClass( "ui-slider" +
199 " ui-slider-horizontal" +
200 " ui-slider-vertical" +
201 " ui-slider-disabled" +
203 " ui-widget-content" +
206 this._mouseDestroy();
209 _mouseCapture: function( event ) {
210 var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
219 width: this.element.outerWidth(),
220 height: this.element.outerHeight()
222 this.elementOffset = this.element.offset();
224 position = { x: event.pageX, y: event.pageY };
225 normValue = this._normValueFromMouse( position );
226 distance = this._valueMax() - this._valueMin() + 1;
227 this.handles.each(function( i ) {
228 var thisDistance = Math.abs( normValue - that.values(i) );
229 if ( distance > thisDistance ) {
230 distance = thisDistance;
231 closestHandle = $( this );
236 // workaround for bug #3736 (if both handles of a range are at 0,
237 // the first is always used as the one with least distance,
238 // and moving it is obviously prevented by preventing negative ranges)
239 if( o.range === true && this.values(1) === o.min ) {
241 closestHandle = $( this.handles[index] );
244 allowed = this._start( event, index );
245 if ( allowed === false ) {
248 this._mouseSliding = true;
250 this._handleIndex = index;
253 .addClass( "ui-state-active" )
256 offset = closestHandle.offset();
257 mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
258 this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
259 left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
260 top: event.pageY - offset.top -
261 ( closestHandle.height() / 2 ) -
262 ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
263 ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
264 ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
267 if ( !this.handles.hasClass( "ui-state-hover" ) ) {
268 this._slide( event, index, normValue );
270 this._animateOff = true;
274 _mouseStart: function() {
278 _mouseDrag: function( event ) {
279 var position = { x: event.pageX, y: event.pageY },
280 normValue = this._normValueFromMouse( position );
282 this._slide( event, this._handleIndex, normValue );
287 _mouseStop: function( event ) {
288 this.handles.removeClass( "ui-state-active" );
289 this._mouseSliding = false;
291 this._stop( event, this._handleIndex );
292 this._change( event, this._handleIndex );
294 this._handleIndex = null;
295 this._clickOffset = null;
296 this._animateOff = false;
301 _detectOrientation: function() {
302 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
305 _normValueFromMouse: function( position ) {
312 if ( this.orientation === "horizontal" ) {
313 pixelTotal = this.elementSize.width;
314 pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
316 pixelTotal = this.elementSize.height;
317 pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
320 percentMouse = ( pixelMouse / pixelTotal );
321 if ( percentMouse > 1 ) {
324 if ( percentMouse < 0 ) {
327 if ( this.orientation === "vertical" ) {
328 percentMouse = 1 - percentMouse;
331 valueTotal = this._valueMax() - this._valueMin();
332 valueMouse = this._valueMin() + percentMouse * valueTotal;
334 return this._trimAlignValue( valueMouse );
337 _start: function( event, index ) {
339 handle: this.handles[ index ],
342 if ( this.options.values && this.options.values.length ) {
343 uiHash.value = this.values( index );
344 uiHash.values = this.values();
346 return this._trigger( "start", event, uiHash );
349 _slide: function( event, index, newVal ) {
354 if ( this.options.values && this.options.values.length ) {
355 otherVal = this.values( index ? 0 : 1 );
357 if ( ( this.options.values.length === 2 && this.options.range === true ) &&
358 ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
363 if ( newVal !== this.values( index ) ) {
364 newValues = this.values();
365 newValues[ index ] = newVal;
366 // A slide can be canceled by returning false from the slide callback
367 allowed = this._trigger( "slide", event, {
368 handle: this.handles[ index ],
372 otherVal = this.values( index ? 0 : 1 );
373 if ( allowed !== false ) {
374 this.values( index, newVal, true );
378 if ( newVal !== this.value() ) {
379 // A slide can be canceled by returning false from the slide callback
380 allowed = this._trigger( "slide", event, {
381 handle: this.handles[ index ],
384 if ( allowed !== false ) {
385 this.value( newVal );
391 _stop: function( event, index ) {
393 handle: this.handles[ index ],
396 if ( this.options.values && this.options.values.length ) {
397 uiHash.value = this.values( index );
398 uiHash.values = this.values();
401 this._trigger( "stop", event, uiHash );
404 _change: function( event, index ) {
405 if ( !this._keySliding && !this._mouseSliding ) {
407 handle: this.handles[ index ],
410 if ( this.options.values && this.options.values.length ) {
411 uiHash.value = this.values( index );
412 uiHash.values = this.values();
415 this._trigger( "change", event, uiHash );
419 value: function( newValue ) {
420 if ( arguments.length ) {
421 this.options.value = this._trimAlignValue( newValue );
422 this._refreshValue();
423 this._change( null, 0 );
427 return this._value();
430 values: function( index, newValue ) {
435 if ( arguments.length > 1 ) {
436 this.options.values[ index ] = this._trimAlignValue( newValue );
437 this._refreshValue();
438 this._change( null, index );
442 if ( arguments.length ) {
443 if ( $.isArray( arguments[ 0 ] ) ) {
444 vals = this.options.values;
445 newValues = arguments[ 0 ];
446 for ( i = 0; i < vals.length; i += 1 ) {
447 vals[ i ] = this._trimAlignValue( newValues[ i ] );
448 this._change( null, i );
450 this._refreshValue();
452 if ( this.options.values && this.options.values.length ) {
453 return this._values( index );
459 return this._values();
463 _setOption: function( key, value ) {
467 if ( $.isArray( this.options.values ) ) {
468 valsLength = this.options.values.length;
471 $.Widget.prototype._setOption.apply( this, arguments );
476 this.handles.filter( ".ui-state-focus" ).blur();
477 this.handles.removeClass( "ui-state-hover" );
478 this.handles.prop( "disabled", true );
479 this.element.addClass( "ui-disabled" );
481 this.handles.prop( "disabled", false );
482 this.element.removeClass( "ui-disabled" );
486 this._detectOrientation();
488 .removeClass( "ui-slider-horizontal ui-slider-vertical" )
489 .addClass( "ui-slider-" + this.orientation );
490 this._refreshValue();
493 this._animateOff = true;
494 this._refreshValue();
495 this._change( null, 0 );
496 this._animateOff = false;
499 this._animateOff = true;
500 this._refreshValue();
501 for ( i = 0; i < valsLength; i += 1 ) {
502 this._change( null, i );
504 this._animateOff = false;
508 this._animateOff = true;
509 this._refreshValue();
510 this._animateOff = false;
515 //internal value getter
516 // _value() returns value trimmed by min and max, aligned by step
518 var val = this.options.value;
519 val = this._trimAlignValue( val );
524 //internal values getter
525 // _values() returns array of values trimmed by min and max, aligned by step
526 // _values( index ) returns single value trimmed by min and max, aligned by step
527 _values: function( index ) {
532 if ( arguments.length ) {
533 val = this.options.values[ index ];
534 val = this._trimAlignValue( val );
538 // .slice() creates a copy of the array
539 // this copy gets trimmed by min and max and then returned
540 vals = this.options.values.slice();
541 for ( i = 0; i < vals.length; i+= 1) {
542 vals[ i ] = this._trimAlignValue( vals[ i ] );
549 // returns the step-aligned value that val is closest to, between (inclusive) min and max
550 _trimAlignValue: function( val ) {
551 if ( val <= this._valueMin() ) {
552 return this._valueMin();
554 if ( val >= this._valueMax() ) {
555 return this._valueMax();
557 var step = ( this.options.step > 0 ) ? this.options.step : 1,
558 valModStep = (val - this._valueMin()) % step,
559 alignValue = val - valModStep;
561 if ( Math.abs(valModStep) * 2 >= step ) {
562 alignValue += ( valModStep > 0 ) ? step : ( -step );
565 // Since JavaScript has problems with large floats, round
566 // the final value to 5 digits after the decimal point (see #4124)
567 return parseFloat( alignValue.toFixed(5) );
570 _valueMin: function() {
571 return this.options.min;
574 _valueMax: function() {
575 return this.options.max;
578 _refreshValue: function() {
579 var lastValPercent, valPercent, value, valueMin, valueMax,
580 oRange = this.options.range,
583 animate = ( !this._animateOff ) ? o.animate : false,
586 if ( this.options.values && this.options.values.length ) {
587 this.handles.each(function( i ) {
588 valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
589 _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
590 $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
591 if ( that.options.range === true ) {
592 if ( that.orientation === "horizontal" ) {
594 that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
597 that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
601 that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
604 that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
608 lastValPercent = valPercent;
611 value = this.value();
612 valueMin = this._valueMin();
613 valueMax = this._valueMax();
614 valPercent = ( valueMax !== valueMin ) ?
615 ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
617 _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
618 this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
620 if ( oRange === "min" && this.orientation === "horizontal" ) {
621 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
623 if ( oRange === "max" && this.orientation === "horizontal" ) {
624 this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
626 if ( oRange === "min" && this.orientation === "vertical" ) {
627 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
629 if ( oRange === "max" && this.orientation === "vertical" ) {
630 this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );