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 (function( $, undefined ) {
9 // number of pages in a slider
10 // (how many times can you page up/down to go through the whole range)
13 $.widget( "ui.slider", $.ui.mouse, {
15 widgetEventPrefix: "slide",
22 orientation: "horizontal",
32 existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
33 handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
36 this._keySliding = false;
37 this._mouseSliding = false;
38 this._animateOff = true;
39 this._handleIndex = null;
40 this._detectOrientation();
44 .addClass( "ui-slider" +
45 " ui-slider-" + this.orientation +
47 " ui-widget-content" +
49 ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
54 if ( o.range === true ) {
56 o.values = [ this._valueMin(), this._valueMin() ];
58 if ( o.values.length && o.values.length !== 2 ) {
59 o.values = [ o.values[0], o.values[0] ];
63 this.range = $( "<div></div>" )
64 .appendTo( this.element )
65 .addClass( "ui-slider-range" +
66 // note: this isn't the most fittingly semantic framework class for this element,
67 // but worked best visually with a variety of themes
69 ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
72 handleCount = ( o.values && o.values.length ) || 1;
74 for ( i = existingHandles.length; i < handleCount; i++ ) {
75 handles.push( handle );
78 this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
80 this.handle = this.handles.eq( 0 );
82 this.handles.add( this.range ).filter( "a" )
83 .click(function( event ) {
84 event.preventDefault();
86 .mouseenter(function() {
88 $( this ).addClass( "ui-state-hover" );
91 .mouseleave(function() {
92 $( this ).removeClass( "ui-state-hover" );
96 $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
97 $( this ).addClass( "ui-state-focus" );
103 $( this ).removeClass( "ui-state-focus" );
106 this.handles.each(function( i ) {
107 $( this ).data( "ui-slider-handle-index", i );
110 this._on( this.handles, {
111 keydown: function( event ) {
112 var allowed, curVal, newVal, step,
113 index = $( event.target ).data( "ui-slider-handle-index" );
115 switch ( event.keyCode ) {
116 case $.ui.keyCode.HOME:
117 case $.ui.keyCode.END:
118 case $.ui.keyCode.PAGE_UP:
119 case $.ui.keyCode.PAGE_DOWN:
120 case $.ui.keyCode.UP:
121 case $.ui.keyCode.RIGHT:
122 case $.ui.keyCode.DOWN:
123 case $.ui.keyCode.LEFT:
124 event.preventDefault();
125 if ( !this._keySliding ) {
126 this._keySliding = true;
127 $( event.target ).addClass( "ui-state-active" );
128 allowed = this._start( event, index );
129 if ( allowed === false ) {
136 step = this.options.step;
137 if ( this.options.values && this.options.values.length ) {
138 curVal = newVal = this.values( index );
140 curVal = newVal = this.value();
143 switch ( event.keyCode ) {
144 case $.ui.keyCode.HOME:
145 newVal = this._valueMin();
147 case $.ui.keyCode.END:
148 newVal = this._valueMax();
150 case $.ui.keyCode.PAGE_UP:
151 newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
153 case $.ui.keyCode.PAGE_DOWN:
154 newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
156 case $.ui.keyCode.UP:
157 case $.ui.keyCode.RIGHT:
158 if ( curVal === this._valueMax() ) {
161 newVal = this._trimAlignValue( curVal + step );
163 case $.ui.keyCode.DOWN:
164 case $.ui.keyCode.LEFT:
165 if ( curVal === this._valueMin() ) {
168 newVal = this._trimAlignValue( curVal - step );
172 this._slide( event, index, newVal );
174 keyup: function( event ) {
175 var index = $( event.target ).data( "ui-slider-handle-index" );
177 if ( this._keySliding ) {
178 this._keySliding = false;
179 this._stop( event, index );
180 this._change( event, index );
181 $( event.target ).removeClass( "ui-state-active" );
186 this._refreshValue();
188 this._animateOff = false;
191 _destroy: function() {
192 this.handles.remove();
196 .removeClass( "ui-slider" +
197 " ui-slider-horizontal" +
198 " ui-slider-vertical" +
199 " ui-slider-disabled" +
201 " ui-widget-content" +
204 this._mouseDestroy();
207 _mouseCapture: function( event ) {
208 var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
217 width: this.element.outerWidth(),
218 height: this.element.outerHeight()
220 this.elementOffset = this.element.offset();
222 position = { x: event.pageX, y: event.pageY };
223 normValue = this._normValueFromMouse( position );
224 distance = this._valueMax() - this._valueMin() + 1;
225 this.handles.each(function( i ) {
226 var thisDistance = Math.abs( normValue - that.values(i) );
227 if ( distance > thisDistance ) {
228 distance = thisDistance;
229 closestHandle = $( this );
234 // workaround for bug #3736 (if both handles of a range are at 0,
235 // the first is always used as the one with least distance,
236 // and moving it is obviously prevented by preventing negative ranges)
237 if( o.range === true && this.values(1) === o.min ) {
239 closestHandle = $( this.handles[index] );
242 allowed = this._start( event, index );
243 if ( allowed === false ) {
246 this._mouseSliding = true;
248 this._handleIndex = index;
251 .addClass( "ui-state-active" )
254 offset = closestHandle.offset();
255 mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
256 this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
257 left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
258 top: event.pageY - offset.top -
259 ( closestHandle.height() / 2 ) -
260 ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
261 ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
262 ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
265 if ( !this.handles.hasClass( "ui-state-hover" ) ) {
266 this._slide( event, index, normValue );
268 this._animateOff = true;
272 _mouseStart: function() {
276 _mouseDrag: function( event ) {
277 var position = { x: event.pageX, y: event.pageY },
278 normValue = this._normValueFromMouse( position );
280 this._slide( event, this._handleIndex, normValue );
285 _mouseStop: function( event ) {
286 this.handles.removeClass( "ui-state-active" );
287 this._mouseSliding = false;
289 this._stop( event, this._handleIndex );
290 this._change( event, this._handleIndex );
292 this._handleIndex = null;
293 this._clickOffset = null;
294 this._animateOff = false;
299 _detectOrientation: function() {
300 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
303 _normValueFromMouse: function( position ) {
310 if ( this.orientation === "horizontal" ) {
311 pixelTotal = this.elementSize.width;
312 pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
314 pixelTotal = this.elementSize.height;
315 pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
318 percentMouse = ( pixelMouse / pixelTotal );
319 if ( percentMouse > 1 ) {
322 if ( percentMouse < 0 ) {
325 if ( this.orientation === "vertical" ) {
326 percentMouse = 1 - percentMouse;
329 valueTotal = this._valueMax() - this._valueMin();
330 valueMouse = this._valueMin() + percentMouse * valueTotal;
332 return this._trimAlignValue( valueMouse );
335 _start: function( event, index ) {
337 handle: this.handles[ index ],
340 if ( this.options.values && this.options.values.length ) {
341 uiHash.value = this.values( index );
342 uiHash.values = this.values();
344 return this._trigger( "start", event, uiHash );
347 _slide: function( event, index, newVal ) {
352 if ( this.options.values && this.options.values.length ) {
353 otherVal = this.values( index ? 0 : 1 );
355 if ( ( this.options.values.length === 2 && this.options.range === true ) &&
356 ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
361 if ( newVal !== this.values( index ) ) {
362 newValues = this.values();
363 newValues[ index ] = newVal;
364 // A slide can be canceled by returning false from the slide callback
365 allowed = this._trigger( "slide", event, {
366 handle: this.handles[ index ],
370 otherVal = this.values( index ? 0 : 1 );
371 if ( allowed !== false ) {
372 this.values( index, newVal, true );
376 if ( newVal !== this.value() ) {
377 // A slide can be canceled by returning false from the slide callback
378 allowed = this._trigger( "slide", event, {
379 handle: this.handles[ index ],
382 if ( allowed !== false ) {
383 this.value( newVal );
389 _stop: function( event, index ) {
391 handle: this.handles[ index ],
394 if ( this.options.values && this.options.values.length ) {
395 uiHash.value = this.values( index );
396 uiHash.values = this.values();
399 this._trigger( "stop", event, uiHash );
402 _change: function( event, index ) {
403 if ( !this._keySliding && !this._mouseSliding ) {
405 handle: this.handles[ index ],
408 if ( this.options.values && this.options.values.length ) {
409 uiHash.value = this.values( index );
410 uiHash.values = this.values();
413 this._trigger( "change", event, uiHash );
417 value: function( newValue ) {
418 if ( arguments.length ) {
419 this.options.value = this._trimAlignValue( newValue );
420 this._refreshValue();
421 this._change( null, 0 );
425 return this._value();
428 values: function( index, newValue ) {
433 if ( arguments.length > 1 ) {
434 this.options.values[ index ] = this._trimAlignValue( newValue );
435 this._refreshValue();
436 this._change( null, index );
440 if ( arguments.length ) {
441 if ( $.isArray( arguments[ 0 ] ) ) {
442 vals = this.options.values;
443 newValues = arguments[ 0 ];
444 for ( i = 0; i < vals.length; i += 1 ) {
445 vals[ i ] = this._trimAlignValue( newValues[ i ] );
446 this._change( null, i );
448 this._refreshValue();
450 if ( this.options.values && this.options.values.length ) {
451 return this._values( index );
457 return this._values();
461 _setOption: function( key, value ) {
465 if ( $.isArray( this.options.values ) ) {
466 valsLength = this.options.values.length;
469 $.Widget.prototype._setOption.apply( this, arguments );
474 this.handles.filter( ".ui-state-focus" ).blur();
475 this.handles.removeClass( "ui-state-hover" );
476 this.handles.prop( "disabled", true );
477 this.element.addClass( "ui-disabled" );
479 this.handles.prop( "disabled", false );
480 this.element.removeClass( "ui-disabled" );
484 this._detectOrientation();
486 .removeClass( "ui-slider-horizontal ui-slider-vertical" )
487 .addClass( "ui-slider-" + this.orientation );
488 this._refreshValue();
491 this._animateOff = true;
492 this._refreshValue();
493 this._change( null, 0 );
494 this._animateOff = false;
497 this._animateOff = true;
498 this._refreshValue();
499 for ( i = 0; i < valsLength; i += 1 ) {
500 this._change( null, i );
502 this._animateOff = false;
506 this._animateOff = true;
507 this._refreshValue();
508 this._animateOff = false;
513 //internal value getter
514 // _value() returns value trimmed by min and max, aligned by step
516 var val = this.options.value;
517 val = this._trimAlignValue( val );
522 //internal values getter
523 // _values() returns array of values trimmed by min and max, aligned by step
524 // _values( index ) returns single value trimmed by min and max, aligned by step
525 _values: function( index ) {
530 if ( arguments.length ) {
531 val = this.options.values[ index ];
532 val = this._trimAlignValue( val );
536 // .slice() creates a copy of the array
537 // this copy gets trimmed by min and max and then returned
538 vals = this.options.values.slice();
539 for ( i = 0; i < vals.length; i+= 1) {
540 vals[ i ] = this._trimAlignValue( vals[ i ] );
547 // returns the step-aligned value that val is closest to, between (inclusive) min and max
548 _trimAlignValue: function( val ) {
549 if ( val <= this._valueMin() ) {
550 return this._valueMin();
552 if ( val >= this._valueMax() ) {
553 return this._valueMax();
555 var step = ( this.options.step > 0 ) ? this.options.step : 1,
556 valModStep = (val - this._valueMin()) % step,
557 alignValue = val - valModStep;
559 if ( Math.abs(valModStep) * 2 >= step ) {
560 alignValue += ( valModStep > 0 ) ? step : ( -step );
563 // Since JavaScript has problems with large floats, round
564 // the final value to 5 digits after the decimal point (see #4124)
565 return parseFloat( alignValue.toFixed(5) );
568 _valueMin: function() {
569 return this.options.min;
572 _valueMax: function() {
573 return this.options.max;
576 _refreshValue: function() {
577 var lastValPercent, valPercent, value, valueMin, valueMax,
578 oRange = this.options.range,
581 animate = ( !this._animateOff ) ? o.animate : false,
584 if ( this.options.values && this.options.values.length ) {
585 this.handles.each(function( i ) {
586 valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
587 _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
588 $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
589 if ( that.options.range === true ) {
590 if ( that.orientation === "horizontal" ) {
592 that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
595 that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
599 that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
602 that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
606 lastValPercent = valPercent;
609 value = this.value();
610 valueMin = this._valueMin();
611 valueMax = this._valueMax();
612 valPercent = ( valueMax !== valueMin ) ?
613 ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
615 _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
616 this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
618 if ( oRange === "min" && this.orientation === "horizontal" ) {
619 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
621 if ( oRange === "max" && this.orientation === "horizontal" ) {
622 this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
624 if ( oRange === "min" && this.orientation === "vertical" ) {
625 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
627 if ( oRange === "max" && this.orientation === "vertical" ) {
628 this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );