2 * jQuery UI Slider 1.9.2
5 * Copyright 2012 jQuery Foundation and other contributors
6 * Released under the MIT license.
7 * http://jquery.org/license
9 * http://api.jqueryui.com/slider/
16 (function( $, undefined ) {
18 // number of pages in a slider
19 // (how many times can you page up/down to go through the whole range)
22 $.widget( "ui.slider", $.ui
.mouse
, {
24 widgetEventPrefix
: "slide",
31 orientation
: "horizontal",
41 existingHandles
= this.element
.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
42 handle
= "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
45 this._keySliding
= false;
46 this._mouseSliding
= false;
47 this._animateOff
= true;
48 this._handleIndex
= null;
49 this._detectOrientation();
53 .addClass( "ui-slider" +
54 " ui-slider-" + this.orientation
+
56 " ui-widget-content" +
58 ( o
.disabled
? " ui-slider-disabled ui-disabled" : "" ) );
63 if ( o
.range
=== true ) {
65 o
.values
= [ this._valueMin(), this._valueMin() ];
67 if ( o
.values
.length
&& o
.values
.length
!== 2 ) {
68 o
.values
= [ o
.values
[0], o
.values
[0] ];
72 this.range
= $( "<div></div>" )
73 .appendTo( this.element
)
74 .addClass( "ui-slider-range" +
75 // note: this isn't the most fittingly semantic framework class for this element,
76 // but worked best visually with a variety of themes
78 ( ( o
.range
=== "min" || o
.range
=== "max" ) ? " ui-slider-range-" + o
.range
: "" ) );
81 handleCount
= ( o
.values
&& o
.values
.length
) || 1;
83 for ( i
= existingHandles
.length
; i
< handleCount
; i
++ ) {
84 handles
.push( handle
);
87 this.handles
= existingHandles
.add( $( handles
.join( "" ) ).appendTo( this.element
) );
89 this.handle
= this.handles
.eq( 0 );
91 this.handles
.add( this.range
).filter( "a" )
92 .click(function( event
) {
93 event
.preventDefault();
95 .mouseenter(function() {
97 $( this ).addClass( "ui-state-hover" );
100 .mouseleave(function() {
101 $( this ).removeClass( "ui-state-hover" );
105 $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
106 $( this ).addClass( "ui-state-focus" );
112 $( this ).removeClass( "ui-state-focus" );
115 this.handles
.each(function( i
) {
116 $( this ).data( "ui-slider-handle-index", i
);
119 this._on( this.handles
, {
120 keydown: function( event
) {
121 var allowed
, curVal
, newVal
, step
,
122 index
= $( event
.target
).data( "ui-slider-handle-index" );
124 switch ( event
.keyCode
) {
125 case $.ui
.keyCode
.HOME
:
126 case $.ui
.keyCode
.END
:
127 case $.ui
.keyCode
.PAGE_UP
:
128 case $.ui
.keyCode
.PAGE_DOWN
:
129 case $.ui
.keyCode
.UP
:
130 case $.ui
.keyCode
.RIGHT
:
131 case $.ui
.keyCode
.DOWN
:
132 case $.ui
.keyCode
.LEFT
:
133 event
.preventDefault();
134 if ( !this._keySliding
) {
135 this._keySliding
= true;
136 $( event
.target
).addClass( "ui-state-active" );
137 allowed
= this._start( event
, index
);
138 if ( allowed
=== false ) {
145 step
= this.options
.step
;
146 if ( this.options
.values
&& this.options
.values
.length
) {
147 curVal
= newVal
= this.values( index
);
149 curVal
= newVal
= this.value();
152 switch ( event
.keyCode
) {
153 case $.ui
.keyCode
.HOME
:
154 newVal
= this._valueMin();
156 case $.ui
.keyCode
.END
:
157 newVal
= this._valueMax();
159 case $.ui
.keyCode
.PAGE_UP
:
160 newVal
= this._trimAlignValue( curVal
+ ( (this._valueMax() - this._valueMin()) / numPages
) );
162 case $.ui
.keyCode
.PAGE_DOWN
:
163 newVal
= this._trimAlignValue( curVal
- ( (this._valueMax() - this._valueMin()) / numPages
) );
165 case $.ui
.keyCode
.UP
:
166 case $.ui
.keyCode
.RIGHT
:
167 if ( curVal
=== this._valueMax() ) {
170 newVal
= this._trimAlignValue( curVal
+ step
);
172 case $.ui
.keyCode
.DOWN
:
173 case $.ui
.keyCode
.LEFT
:
174 if ( curVal
=== this._valueMin() ) {
177 newVal
= this._trimAlignValue( curVal
- step
);
181 this._slide( event
, index
, newVal
);
183 keyup: function( event
) {
184 var index
= $( event
.target
).data( "ui-slider-handle-index" );
186 if ( this._keySliding
) {
187 this._keySliding
= false;
188 this._stop( event
, index
);
189 this._change( event
, index
);
190 $( event
.target
).removeClass( "ui-state-active" );
195 this._refreshValue();
197 this._animateOff
= false;
200 _destroy: function() {
201 this.handles
.remove();
205 .removeClass( "ui-slider" +
206 " ui-slider-horizontal" +
207 " ui-slider-vertical" +
208 " ui-slider-disabled" +
210 " ui-widget-content" +
213 this._mouseDestroy();
216 _mouseCapture: function( event
) {
217 var position
, normValue
, distance
, closestHandle
, index
, allowed
, offset
, mouseOverHandle
,
226 width
: this.element
.outerWidth(),
227 height
: this.element
.outerHeight()
229 this.elementOffset
= this.element
.offset();
231 position
= { x
: event
.pageX
, y
: event
.pageY
};
232 normValue
= this._normValueFromMouse( position
);
233 distance
= this._valueMax() - this._valueMin() + 1;
234 this.handles
.each(function( i
) {
235 var thisDistance
= Math
.abs( normValue
- that
.values(i
) );
236 if ( distance
> thisDistance
) {
237 distance
= thisDistance
;
238 closestHandle
= $( this );
243 // workaround for bug #3736 (if both handles of a range are at 0,
244 // the first is always used as the one with least distance,
245 // and moving it is obviously prevented by preventing negative ranges)
246 if( o
.range
=== true && this.values(1) === o
.min
) {
248 closestHandle
= $( this.handles
[index
] );
251 allowed
= this._start( event
, index
);
252 if ( allowed
=== false ) {
255 this._mouseSliding
= true;
257 this._handleIndex
= index
;
260 .addClass( "ui-state-active" )
263 offset
= closestHandle
.offset();
264 mouseOverHandle
= !$( event
.target
).parents().andSelf().is( ".ui-slider-handle" );
265 this._clickOffset
= mouseOverHandle
? { left
: 0, top
: 0 } : {
266 left
: event
.pageX
- offset
.left
- ( closestHandle
.width() / 2 ),
267 top
: event
.pageY
- offset
.top
-
268 ( closestHandle
.height() / 2 ) -
269 ( parseInt( closestHandle
.css("borderTopWidth"), 10 ) || 0 ) -
270 ( parseInt( closestHandle
.css("borderBottomWidth"), 10 ) || 0) +
271 ( parseInt( closestHandle
.css("marginTop"), 10 ) || 0)
274 if ( !this.handles
.hasClass( "ui-state-hover" ) ) {
275 this._slide( event
, index
, normValue
);
277 this._animateOff
= true;
281 _mouseStart: function() {
285 _mouseDrag: function( event
) {
286 var position
= { x
: event
.pageX
, y
: event
.pageY
},
287 normValue
= this._normValueFromMouse( position
);
289 this._slide( event
, this._handleIndex
, normValue
);
294 _mouseStop: function( event
) {
295 this.handles
.removeClass( "ui-state-active" );
296 this._mouseSliding
= false;
298 this._stop( event
, this._handleIndex
);
299 this._change( event
, this._handleIndex
);
301 this._handleIndex
= null;
302 this._clickOffset
= null;
303 this._animateOff
= false;
308 _detectOrientation: function() {
309 this.orientation
= ( this.options
.orientation
=== "vertical" ) ? "vertical" : "horizontal";
312 _normValueFromMouse: function( position
) {
319 if ( this.orientation
=== "horizontal" ) {
320 pixelTotal
= this.elementSize
.width
;
321 pixelMouse
= position
.x
- this.elementOffset
.left
- ( this._clickOffset
? this._clickOffset
.left
: 0 );
323 pixelTotal
= this.elementSize
.height
;
324 pixelMouse
= position
.y
- this.elementOffset
.top
- ( this._clickOffset
? this._clickOffset
.top
: 0 );
327 percentMouse
= ( pixelMouse
/ pixelTotal
);
328 if ( percentMouse
> 1 ) {
331 if ( percentMouse
< 0 ) {
334 if ( this.orientation
=== "vertical" ) {
335 percentMouse
= 1 - percentMouse
;
338 valueTotal
= this._valueMax() - this._valueMin();
339 valueMouse
= this._valueMin() + percentMouse
* valueTotal
;
341 return this._trimAlignValue( valueMouse
);
344 _start: function( event
, index
) {
346 handle
: this.handles
[ index
],
349 if ( this.options
.values
&& this.options
.values
.length
) {
350 uiHash
.value
= this.values( index
);
351 uiHash
.values
= this.values();
353 return this._trigger( "start", event
, uiHash
);
356 _slide: function( event
, index
, newVal
) {
361 if ( this.options
.values
&& this.options
.values
.length
) {
362 otherVal
= this.values( index
? 0 : 1 );
364 if ( ( this.options
.values
.length
=== 2 && this.options
.range
=== true ) &&
365 ( ( index
=== 0 && newVal
> otherVal
) || ( index
=== 1 && newVal
< otherVal
) )
370 if ( newVal
!== this.values( index
) ) {
371 newValues
= this.values();
372 newValues
[ index
] = newVal
;
373 // A slide can be canceled by returning false from the slide callback
374 allowed
= this._trigger( "slide", event
, {
375 handle
: this.handles
[ index
],
379 otherVal
= this.values( index
? 0 : 1 );
380 if ( allowed
!== false ) {
381 this.values( index
, newVal
, true );
385 if ( newVal
!== this.value() ) {
386 // A slide can be canceled by returning false from the slide callback
387 allowed
= this._trigger( "slide", event
, {
388 handle
: this.handles
[ index
],
391 if ( allowed
!== false ) {
392 this.value( newVal
);
398 _stop: function( event
, index
) {
400 handle
: this.handles
[ index
],
403 if ( this.options
.values
&& this.options
.values
.length
) {
404 uiHash
.value
= this.values( index
);
405 uiHash
.values
= this.values();
408 this._trigger( "stop", event
, uiHash
);
411 _change: function( event
, index
) {
412 if ( !this._keySliding
&& !this._mouseSliding
) {
414 handle
: this.handles
[ index
],
417 if ( this.options
.values
&& this.options
.values
.length
) {
418 uiHash
.value
= this.values( index
);
419 uiHash
.values
= this.values();
422 this._trigger( "change", event
, uiHash
);
426 value: function( newValue
) {
427 if ( arguments
.length
) {
428 this.options
.value
= this._trimAlignValue( newValue
);
429 this._refreshValue();
430 this._change( null, 0 );
434 return this._value();
437 values: function( index
, newValue
) {
442 if ( arguments
.length
> 1 ) {
443 this.options
.values
[ index
] = this._trimAlignValue( newValue
);
444 this._refreshValue();
445 this._change( null, index
);
449 if ( arguments
.length
) {
450 if ( $.isArray( arguments
[ 0 ] ) ) {
451 vals
= this.options
.values
;
452 newValues
= arguments
[ 0 ];
453 for ( i
= 0; i
< vals
.length
; i
+= 1 ) {
454 vals
[ i
] = this._trimAlignValue( newValues
[ i
] );
455 this._change( null, i
);
457 this._refreshValue();
459 if ( this.options
.values
&& this.options
.values
.length
) {
460 return this._values( index
);
466 return this._values();
470 _setOption: function( key
, value
) {
474 if ( $.isArray( this.options
.values
) ) {
475 valsLength
= this.options
.values
.length
;
478 $.Widget
.prototype._setOption
.apply( this, arguments
);
483 this.handles
.filter( ".ui-state-focus" ).blur();
484 this.handles
.removeClass( "ui-state-hover" );
485 this.handles
.prop( "disabled", true );
486 this.element
.addClass( "ui-disabled" );
488 this.handles
.prop( "disabled", false );
489 this.element
.removeClass( "ui-disabled" );
493 this._detectOrientation();
495 .removeClass( "ui-slider-horizontal ui-slider-vertical" )
496 .addClass( "ui-slider-" + this.orientation
);
497 this._refreshValue();
500 this._animateOff
= true;
501 this._refreshValue();
502 this._change( null, 0 );
503 this._animateOff
= false;
506 this._animateOff
= true;
507 this._refreshValue();
508 for ( i
= 0; i
< valsLength
; i
+= 1 ) {
509 this._change( null, i
);
511 this._animateOff
= false;
515 this._animateOff
= true;
516 this._refreshValue();
517 this._animateOff
= false;
522 //internal value getter
523 // _value() returns value trimmed by min and max, aligned by step
525 var val
= this.options
.value
;
526 val
= this._trimAlignValue( val
);
531 //internal values getter
532 // _values() returns array of values trimmed by min and max, aligned by step
533 // _values( index ) returns single value trimmed by min and max, aligned by step
534 _values: function( index
) {
539 if ( arguments
.length
) {
540 val
= this.options
.values
[ index
];
541 val
= this._trimAlignValue( val
);
545 // .slice() creates a copy of the array
546 // this copy gets trimmed by min and max and then returned
547 vals
= this.options
.values
.slice();
548 for ( i
= 0; i
< vals
.length
; i
+= 1) {
549 vals
[ i
] = this._trimAlignValue( vals
[ i
] );
556 // returns the step-aligned value that val is closest to, between (inclusive) min and max
557 _trimAlignValue: function( val
) {
558 if ( val
<= this._valueMin() ) {
559 return this._valueMin();
561 if ( val
>= this._valueMax() ) {
562 return this._valueMax();
564 var step
= ( this.options
.step
> 0 ) ? this.options
.step
: 1,
565 valModStep
= (val
- this._valueMin()) % step
,
566 alignValue
= val
- valModStep
;
568 if ( Math
.abs(valModStep
) * 2 >= step
) {
569 alignValue
+= ( valModStep
> 0 ) ? step
: ( -step
);
572 // Since JavaScript has problems with large floats, round
573 // the final value to 5 digits after the decimal point (see #4124)
574 return parseFloat( alignValue
.toFixed(5) );
577 _valueMin: function() {
578 return this.options
.min
;
581 _valueMax: function() {
582 return this.options
.max
;
585 _refreshValue: function() {
586 var lastValPercent
, valPercent
, value
, valueMin
, valueMax
,
587 oRange
= this.options
.range
,
590 animate
= ( !this._animateOff
) ? o
.animate
: false,
593 if ( this.options
.values
&& this.options
.values
.length
) {
594 this.handles
.each(function( i
) {
595 valPercent
= ( that
.values(i
) - that
._valueMin() ) / ( that
._valueMax() - that
._valueMin() ) * 100;
596 _set
[ that
.orientation
=== "horizontal" ? "left" : "bottom" ] = valPercent
+ "%";
597 $( this ).stop( 1, 1 )[ animate
? "animate" : "css" ]( _set
, o
.animate
);
598 if ( that
.options
.range
=== true ) {
599 if ( that
.orientation
=== "horizontal" ) {
601 that
.range
.stop( 1, 1 )[ animate
? "animate" : "css" ]( { left
: valPercent
+ "%" }, o
.animate
);
604 that
.range
[ animate
? "animate" : "css" ]( { width
: ( valPercent
- lastValPercent
) + "%" }, { queue
: false, duration
: o
.animate
} );
608 that
.range
.stop( 1, 1 )[ animate
? "animate" : "css" ]( { bottom
: ( valPercent
) + "%" }, o
.animate
);
611 that
.range
[ animate
? "animate" : "css" ]( { height
: ( valPercent
- lastValPercent
) + "%" }, { queue
: false, duration
: o
.animate
} );
615 lastValPercent
= valPercent
;
618 value
= this.value();
619 valueMin
= this._valueMin();
620 valueMax
= this._valueMax();
621 valPercent
= ( valueMax
!== valueMin
) ?
622 ( value
- valueMin
) / ( valueMax
- valueMin
) * 100 :
624 _set
[ this.orientation
=== "horizontal" ? "left" : "bottom" ] = valPercent
+ "%";
625 this.handle
.stop( 1, 1 )[ animate
? "animate" : "css" ]( _set
, o
.animate
);
627 if ( oRange
=== "min" && this.orientation
=== "horizontal" ) {
628 this.range
.stop( 1, 1 )[ animate
? "animate" : "css" ]( { width
: valPercent
+ "%" }, o
.animate
);
630 if ( oRange
=== "max" && this.orientation
=== "horizontal" ) {
631 this.range
[ animate
? "animate" : "css" ]( { width
: ( 100 - valPercent
) + "%" }, { queue
: false, duration
: o
.animate
} );
633 if ( oRange
=== "min" && this.orientation
=== "vertical" ) {
634 this.range
.stop( 1, 1 )[ animate
? "animate" : "css" ]( { height
: valPercent
+ "%" }, o
.animate
);
636 if ( oRange
=== "max" && this.orientation
=== "vertical" ) {
637 this.range
[ animate
? "animate" : "css" ]( { height
: ( 100 - valPercent
) + "%" }, { queue
: false, duration
: o
.animate
} );