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 } );