2 * jQuery UI Slider @VERSION
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>",
43 handleCount = ( o.values && o.values.length ) || 1,
46 this._keySliding = false;
47 this._mouseSliding = false;
48 this._animateOff = true;
49 this._handleIndex = null;
50 this._detectOrientation();
54 .addClass( "ui-slider" +
55 " ui-slider-" + this.orientation +
57 " ui-widget-content" +
59 ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
64 if ( o.range === true ) {
66 o.values = [ this._valueMin(), this._valueMin() ];
68 if ( o.values.length && o.values.length !== 2 ) {
69 o.values = [ o.values[0], o.values[0] ];
73 this.range = $( "<div></div>" )
74 .appendTo( this.element )
75 .addClass( "ui-slider-range" +
76 // note: this isn't the most fittingly semantic framework class for this element,
77 // but worked best visually with a variety of themes
79 ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
82 for ( i = existingHandles.length; i < handleCount; i++ ) {
83 handles.push( handle );
86 this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
88 this.handle = this.handles.eq( 0 );
90 this.handles.add( this.range ).filter( "a" )
91 .click(function( event ) {
92 event.preventDefault();
94 .mouseenter(function() {
96 $( this ).addClass( "ui-state-hover" );
99 .mouseleave(function() {
100 $( this ).removeClass( "ui-state-hover" );
104 $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
105 $( this ).addClass( "ui-state-focus" );
111 $( this ).removeClass( "ui-state-focus" );
114 this.handles.each(function( i ) {
115 $( this ).data( "ui-slider-handle-index", i );
118 this._on( this.handles, {
119 keydown: function( event ) {
120 var allowed, curVal, newVal, step,
121 index = $( event.target ).data( "ui-slider-handle-index" );
123 switch ( event.keyCode ) {
124 case $.ui.keyCode.HOME:
125 case $.ui.keyCode.END:
126 case $.ui.keyCode.PAGE_UP:
127 case $.ui.keyCode.PAGE_DOWN:
128 case $.ui.keyCode.UP:
129 case $.ui.keyCode.RIGHT:
130 case $.ui.keyCode.DOWN:
131 case $.ui.keyCode.LEFT:
132 event.preventDefault();
133 if ( !this._keySliding ) {
134 this._keySliding = true;
135 $( event.target ).addClass( "ui-state-active" );
136 allowed = this._start( event, index );
137 if ( allowed === false ) {
144 step = this.options.step;
145 if ( this.options.values && this.options.values.length ) {
146 curVal = newVal = this.values( index );
148 curVal = newVal = this.value();
151 switch ( event.keyCode ) {
152 case $.ui.keyCode.HOME:
153 newVal = this._valueMin();
155 case $.ui.keyCode.END:
156 newVal = this._valueMax();
158 case $.ui.keyCode.PAGE_UP:
159 newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
161 case $.ui.keyCode.PAGE_DOWN:
162 newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
164 case $.ui.keyCode.UP:
165 case $.ui.keyCode.RIGHT:
166 if ( curVal === this._valueMax() ) {
169 newVal = this._trimAlignValue( curVal + step );
171 case $.ui.keyCode.DOWN:
172 case $.ui.keyCode.LEFT:
173 if ( curVal === this._valueMin() ) {
176 newVal = this._trimAlignValue( curVal - step );
180 this._slide( event, index, newVal );
182 keyup: function( event ) {
183 var index = $( event.target ).data( "ui-slider-handle-index" );
185 if ( this._keySliding ) {
186 this._keySliding = false;
187 this._stop( event, index );
188 this._change( event, index );
189 $( event.target ).removeClass( "ui-state-active" );
194 this._refreshValue();
196 this._animateOff = false;
199 _destroy: function() {
200 this.handles.remove();
204 .removeClass( "ui-slider" +
205 " ui-slider-horizontal" +
206 " ui-slider-vertical" +
207 " ui-slider-disabled" +
209 " ui-widget-content" +
212 this._mouseDestroy();
215 _mouseCapture: function( event ) {
216 var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
225 width: this.element.outerWidth(),
226 height: this.element.outerHeight()
228 this.elementOffset = this.element.offset();
230 position = { x: event.pageX, y: event.pageY };
231 normValue = this._normValueFromMouse( position );
232 distance = this._valueMax() - this._valueMin() + 1;
233 this.handles.each(function( i ) {
234 var thisDistance = Math.abs( normValue - that.values(i) );
235 if ( distance > thisDistance ) {
236 distance = thisDistance;
237 closestHandle = $( this );
242 // workaround for bug #3736 (if both handles of a range are at 0,
243 // the first is always used as the one with least distance,
244 // and moving it is obviously prevented by preventing negative ranges)
245 if( o.range === true && this.values(1) === o.min ) {
247 closestHandle = $( this.handles[index] );
250 allowed = this._start( event, index );
251 if ( allowed === false ) {
254 this._mouseSliding = true;
256 this._handleIndex = index;
259 .addClass( "ui-state-active" )
262 offset = closestHandle.offset();
263 mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
264 this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
265 left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
266 top: event.pageY - offset.top -
267 ( closestHandle.height() / 2 ) -
268 ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
269 ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
270 ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
273 if ( !this.handles.hasClass( "ui-state-hover" ) ) {
274 this._slide( event, index, normValue );
276 this._animateOff = true;
280 _mouseStart: function( event ) {
284 _mouseDrag: function( event ) {
285 var position = { x: event.pageX, y: event.pageY },
286 normValue = this._normValueFromMouse( position );
288 this._slide( event, this._handleIndex, normValue );
293 _mouseStop: function( event ) {
294 this.handles.removeClass( "ui-state-active" );
295 this._mouseSliding = false;
297 this._stop( event, this._handleIndex );
298 this._change( event, this._handleIndex );
300 this._handleIndex = null;
301 this._clickOffset = null;
302 this._animateOff = false;
307 _detectOrientation: function() {
308 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
311 _normValueFromMouse: function( position ) {
318 if ( this.orientation === "horizontal" ) {
319 pixelTotal = this.elementSize.width;
320 pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
322 pixelTotal = this.elementSize.height;
323 pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
326 percentMouse = ( pixelMouse / pixelTotal );
327 if ( percentMouse > 1 ) {
330 if ( percentMouse < 0 ) {
333 if ( this.orientation === "vertical" ) {
334 percentMouse = 1 - percentMouse;
337 valueTotal = this._valueMax() - this._valueMin();
338 valueMouse = this._valueMin() + percentMouse * valueTotal;
340 return this._trimAlignValue( valueMouse );
343 _start: function( event, index ) {
345 handle: this.handles[ index ],
348 if ( this.options.values && this.options.values.length ) {
349 uiHash.value = this.values( index );
350 uiHash.values = this.values();
352 return this._trigger( "start", event, uiHash );
355 _slide: function( event, index, newVal ) {
360 if ( this.options.values && this.options.values.length ) {
361 otherVal = this.values( index ? 0 : 1 );
363 if ( ( this.options.values.length === 2 && this.options.range === true ) &&
364 ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
369 if ( newVal !== this.values( index ) ) {
370 newValues = this.values();
371 newValues[ index ] = newVal;
372 // A slide can be canceled by returning false from the slide callback
373 allowed = this._trigger( "slide", event, {
374 handle: this.handles[ index ],
378 otherVal = this.values( index ? 0 : 1 );
379 if ( allowed !== false ) {
380 this.values( index, newVal, true );
384 if ( newVal !== this.value() ) {
385 // A slide can be canceled by returning false from the slide callback
386 allowed = this._trigger( "slide", event, {
387 handle: this.handles[ index ],
390 if ( allowed !== false ) {
391 this.value( newVal );
397 _stop: function( event, index ) {
399 handle: this.handles[ index ],
402 if ( this.options.values && this.options.values.length ) {
403 uiHash.value = this.values( index );
404 uiHash.values = this.values();
407 this._trigger( "stop", event, uiHash );
410 _change: function( event, index ) {
411 if ( !this._keySliding && !this._mouseSliding ) {
413 handle: this.handles[ index ],
416 if ( this.options.values && this.options.values.length ) {
417 uiHash.value = this.values( index );
418 uiHash.values = this.values();
421 this._trigger( "change", event, uiHash );
425 value: function( newValue ) {
426 if ( arguments.length ) {
427 this.options.value = this._trimAlignValue( newValue );
428 this._refreshValue();
429 this._change( null, 0 );
433 return this._value();
436 values: function( index, newValue ) {
441 if ( arguments.length > 1 ) {
442 this.options.values[ index ] = this._trimAlignValue( newValue );
443 this._refreshValue();
444 this._change( null, index );
448 if ( arguments.length ) {
449 if ( $.isArray( arguments[ 0 ] ) ) {
450 vals = this.options.values;
451 newValues = arguments[ 0 ];
452 for ( i = 0; i < vals.length; i += 1 ) {
453 vals[ i ] = this._trimAlignValue( newValues[ i ] );
454 this._change( null, i );
456 this._refreshValue();
458 if ( this.options.values && this.options.values.length ) {
459 return this._values( index );
465 return this._values();
469 _setOption: function( key, value ) {
473 if ( $.isArray( this.options.values ) ) {
474 valsLength = this.options.values.length;
477 $.Widget.prototype._setOption.apply( this, arguments );
482 this.handles.filter( ".ui-state-focus" ).blur();
483 this.handles.removeClass( "ui-state-hover" );
484 this.handles.prop( "disabled", true );
485 this.element.addClass( "ui-disabled" );
487 this.handles.prop( "disabled", false );
488 this.element.removeClass( "ui-disabled" );
492 this._detectOrientation();
494 .removeClass( "ui-slider-horizontal ui-slider-vertical" )
495 .addClass( "ui-slider-" + this.orientation );
496 this._refreshValue();
499 this._animateOff = true;
500 this._refreshValue();
501 this._change( null, 0 );
502 this._animateOff = false;
505 this._animateOff = true;
506 this._refreshValue();
507 for ( i = 0; i < valsLength; i += 1 ) {
508 this._change( null, i );
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, j ) {
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 } );