2 * jQuery UI Slider 1.8.24
4 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
8 * http://docs.jquery.com/UI/Slider
15 (function( $, undefined ) {
17 // number of pages in a slider
18 // (how many times can you page up/down to go through the whole range)
21 $.widget( "ui.slider", $.ui.mouse, {
23 widgetEventPrefix: "slide",
30 orientation: "horizontal",
40 existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
41 handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
42 handleCount = ( o.values && o.values.length ) || 1,
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 for ( var i = existingHandles.length; i < handleCount; i += 1 ) {
82 handles.push( handle );
85 this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( self.element ) );
87 this.handle = this.handles.eq( 0 );
89 this.handles.add( this.range ).filter( "a" )
90 .click(function( event ) {
91 event.preventDefault();
95 $( this ).addClass( "ui-state-hover" );
98 $( this ).removeClass( "ui-state-hover" );
102 $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
103 $( this ).addClass( "ui-state-focus" );
109 $( this ).removeClass( "ui-state-focus" );
112 this.handles.each(function( i ) {
113 $( this ).data( "index.ui-slider-handle", i );
117 .keydown(function( event ) {
118 var index = $( this ).data( "index.ui-slider-handle" ),
124 if ( self.options.disabled ) {
128 switch ( event.keyCode ) {
129 case $.ui.keyCode.HOME:
130 case $.ui.keyCode.END:
131 case $.ui.keyCode.PAGE_UP:
132 case $.ui.keyCode.PAGE_DOWN:
133 case $.ui.keyCode.UP:
134 case $.ui.keyCode.RIGHT:
135 case $.ui.keyCode.DOWN:
136 case $.ui.keyCode.LEFT:
137 event.preventDefault();
138 if ( !self._keySliding ) {
139 self._keySliding = true;
140 $( this ).addClass( "ui-state-active" );
141 allowed = self._start( event, index );
142 if ( allowed === false ) {
149 step = self.options.step;
150 if ( self.options.values && self.options.values.length ) {
151 curVal = newVal = self.values( index );
153 curVal = newVal = self.value();
156 switch ( event.keyCode ) {
157 case $.ui.keyCode.HOME:
158 newVal = self._valueMin();
160 case $.ui.keyCode.END:
161 newVal = self._valueMax();
163 case $.ui.keyCode.PAGE_UP:
164 newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) );
166 case $.ui.keyCode.PAGE_DOWN:
167 newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) );
169 case $.ui.keyCode.UP:
170 case $.ui.keyCode.RIGHT:
171 if ( curVal === self._valueMax() ) {
174 newVal = self._trimAlignValue( curVal + step );
176 case $.ui.keyCode.DOWN:
177 case $.ui.keyCode.LEFT:
178 if ( curVal === self._valueMin() ) {
181 newVal = self._trimAlignValue( curVal - step );
185 self._slide( event, index, newVal );
187 .keyup(function( event ) {
188 var index = $( this ).data( "index.ui-slider-handle" );
190 if ( self._keySliding ) {
191 self._keySliding = false;
192 self._stop( event, index );
193 self._change( event, index );
194 $( this ).removeClass( "ui-state-active" );
199 this._refreshValue();
201 this._animateOff = false;
204 destroy: function() {
205 this.handles.remove();
209 .removeClass( "ui-slider" +
210 " ui-slider-horizontal" +
211 " ui-slider-vertical" +
212 " ui-slider-disabled" +
214 " ui-widget-content" +
216 .removeData( "slider" )
217 .unbind( ".slider" );
219 this._mouseDestroy();
224 _mouseCapture: function( event ) {
225 var o = this.options,
241 width: this.element.outerWidth(),
242 height: this.element.outerHeight()
244 this.elementOffset = this.element.offset();
246 position = { x: event.pageX, y: event.pageY };
247 normValue = this._normValueFromMouse( position );
248 distance = this._valueMax() - this._valueMin() + 1;
250 this.handles.each(function( i ) {
251 var thisDistance = Math.abs( normValue - self.values(i) );
252 if ( distance > thisDistance ) {
253 distance = thisDistance;
254 closestHandle = $( this );
259 // workaround for bug #3736 (if both handles of a range are at 0,
260 // the first is always used as the one with least distance,
261 // and moving it is obviously prevented by preventing negative ranges)
262 if( o.range === true && this.values(1) === o.min ) {
264 closestHandle = $( this.handles[index] );
267 allowed = this._start( event, index );
268 if ( allowed === false ) {
271 this._mouseSliding = true;
273 self._handleIndex = index;
276 .addClass( "ui-state-active" )
279 offset = closestHandle.offset();
280 mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
281 this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
282 left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
283 top: event.pageY - offset.top -
284 ( closestHandle.height() / 2 ) -
285 ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
286 ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
287 ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
290 if ( !this.handles.hasClass( "ui-state-hover" ) ) {
291 this._slide( event, index, normValue );
293 this._animateOff = true;
297 _mouseStart: function( event ) {
301 _mouseDrag: function( event ) {
302 var position = { x: event.pageX, y: event.pageY },
303 normValue = this._normValueFromMouse( position );
305 this._slide( event, this._handleIndex, normValue );
310 _mouseStop: function( event ) {
311 this.handles.removeClass( "ui-state-active" );
312 this._mouseSliding = false;
314 this._stop( event, this._handleIndex );
315 this._change( event, this._handleIndex );
317 this._handleIndex = null;
318 this._clickOffset = null;
319 this._animateOff = false;
324 _detectOrientation: function() {
325 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
328 _normValueFromMouse: function( position ) {
335 if ( this.orientation === "horizontal" ) {
336 pixelTotal = this.elementSize.width;
337 pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
339 pixelTotal = this.elementSize.height;
340 pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
343 percentMouse = ( pixelMouse / pixelTotal );
344 if ( percentMouse > 1 ) {
347 if ( percentMouse < 0 ) {
350 if ( this.orientation === "vertical" ) {
351 percentMouse = 1 - percentMouse;
354 valueTotal = this._valueMax() - this._valueMin();
355 valueMouse = this._valueMin() + percentMouse * valueTotal;
357 return this._trimAlignValue( valueMouse );
360 _start: function( event, index ) {
362 handle: this.handles[ index ],
365 if ( this.options.values && this.options.values.length ) {
366 uiHash.value = this.values( index );
367 uiHash.values = this.values();
369 return this._trigger( "start", event, uiHash );
372 _slide: function( event, index, newVal ) {
377 if ( this.options.values && this.options.values.length ) {
378 otherVal = this.values( index ? 0 : 1 );
380 if ( ( this.options.values.length === 2 && this.options.range === true ) &&
381 ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
386 if ( newVal !== this.values( index ) ) {
387 newValues = this.values();
388 newValues[ index ] = newVal;
389 // A slide can be canceled by returning false from the slide callback
390 allowed = this._trigger( "slide", event, {
391 handle: this.handles[ index ],
395 otherVal = this.values( index ? 0 : 1 );
396 if ( allowed !== false ) {
397 this.values( index, newVal, true );
401 if ( newVal !== this.value() ) {
402 // A slide can be canceled by returning false from the slide callback
403 allowed = this._trigger( "slide", event, {
404 handle: this.handles[ index ],
407 if ( allowed !== false ) {
408 this.value( newVal );
414 _stop: function( event, index ) {
416 handle: this.handles[ index ],
419 if ( this.options.values && this.options.values.length ) {
420 uiHash.value = this.values( index );
421 uiHash.values = this.values();
424 this._trigger( "stop", event, uiHash );
427 _change: function( event, index ) {
428 if ( !this._keySliding && !this._mouseSliding ) {
430 handle: this.handles[ index ],
433 if ( this.options.values && this.options.values.length ) {
434 uiHash.value = this.values( index );
435 uiHash.values = this.values();
438 this._trigger( "change", event, uiHash );
442 value: function( newValue ) {
443 if ( arguments.length ) {
444 this.options.value = this._trimAlignValue( newValue );
445 this._refreshValue();
446 this._change( null, 0 );
450 return this._value();
453 values: function( index, newValue ) {
458 if ( arguments.length > 1 ) {
459 this.options.values[ index ] = this._trimAlignValue( newValue );
460 this._refreshValue();
461 this._change( null, index );
465 if ( arguments.length ) {
466 if ( $.isArray( arguments[ 0 ] ) ) {
467 vals = this.options.values;
468 newValues = arguments[ 0 ];
469 for ( i = 0; i < vals.length; i += 1 ) {
470 vals[ i ] = this._trimAlignValue( newValues[ i ] );
471 this._change( null, i );
473 this._refreshValue();
475 if ( this.options.values && this.options.values.length ) {
476 return this._values( index );
482 return this._values();
486 _setOption: function( key, value ) {
490 if ( $.isArray( this.options.values ) ) {
491 valsLength = this.options.values.length;
494 $.Widget.prototype._setOption.apply( this, arguments );
499 this.handles.filter( ".ui-state-focus" ).blur();
500 this.handles.removeClass( "ui-state-hover" );
501 this.handles.propAttr( "disabled", true );
502 this.element.addClass( "ui-disabled" );
504 this.handles.propAttr( "disabled", false );
505 this.element.removeClass( "ui-disabled" );
509 this._detectOrientation();
511 .removeClass( "ui-slider-horizontal ui-slider-vertical" )
512 .addClass( "ui-slider-" + this.orientation );
513 this._refreshValue();
516 this._animateOff = true;
517 this._refreshValue();
518 this._change( null, 0 );
519 this._animateOff = false;
522 this._animateOff = true;
523 this._refreshValue();
524 for ( i = 0; i < valsLength; i += 1 ) {
525 this._change( null, i );
527 this._animateOff = false;
532 //internal value getter
533 // _value() returns value trimmed by min and max, aligned by step
535 var val = this.options.value;
536 val = this._trimAlignValue( val );
541 //internal values getter
542 // _values() returns array of values trimmed by min and max, aligned by step
543 // _values( index ) returns single value trimmed by min and max, aligned by step
544 _values: function( index ) {
549 if ( arguments.length ) {
550 val = this.options.values[ index ];
551 val = this._trimAlignValue( val );
555 // .slice() creates a copy of the array
556 // this copy gets trimmed by min and max and then returned
557 vals = this.options.values.slice();
558 for ( i = 0; i < vals.length; i+= 1) {
559 vals[ i ] = this._trimAlignValue( vals[ i ] );
566 // returns the step-aligned value that val is closest to, between (inclusive) min and max
567 _trimAlignValue: function( val ) {
568 if ( val <= this._valueMin() ) {
569 return this._valueMin();
571 if ( val >= this._valueMax() ) {
572 return this._valueMax();
574 var step = ( this.options.step > 0 ) ? this.options.step : 1,
575 valModStep = (val - this._valueMin()) % step,
576 alignValue = val - valModStep;
578 if ( Math.abs(valModStep) * 2 >= step ) {
579 alignValue += ( valModStep > 0 ) ? step : ( -step );
582 // Since JavaScript has problems with large floats, round
583 // the final value to 5 digits after the decimal point (see #4124)
584 return parseFloat( alignValue.toFixed(5) );
587 _valueMin: function() {
588 return this.options.min;
591 _valueMax: function() {
592 return this.options.max;
595 _refreshValue: function() {
596 var oRange = this.options.range,
599 animate = ( !this._animateOff ) ? o.animate : false,
607 if ( this.options.values && this.options.values.length ) {
608 this.handles.each(function( i, j ) {
609 valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100;
610 _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
611 $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
612 if ( self.options.range === true ) {
613 if ( self.orientation === "horizontal" ) {
615 self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
618 self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
622 self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
625 self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
629 lastValPercent = valPercent;
632 value = this.value();
633 valueMin = this._valueMin();
634 valueMax = this._valueMax();
635 valPercent = ( valueMax !== valueMin ) ?
636 ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
638 _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
639 this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
641 if ( oRange === "min" && this.orientation === "horizontal" ) {
642 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
644 if ( oRange === "max" && this.orientation === "horizontal" ) {
645 this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
647 if ( oRange === "min" && this.orientation === "vertical" ) {
648 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
650 if ( oRange === "max" && this.orientation === "vertical" ) {
651 this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
658 $.extend( $.ui.slider, {