Merge "Whitelist the <wbr> element."
[mediawiki.git] / resources / jquery.ui / jquery.ui.slider.js
blobc554e7836fe3ee75f38c7ee62750afaeae059589
1 /*!
2  * jQuery UI Slider 1.8.24
3  *
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
7  *
8  * http://docs.jquery.com/UI/Slider
9  *
10  * Depends:
11  *      jquery.ui.core.js
12  *      jquery.ui.mouse.js
13  *      jquery.ui.widget.js
14  */
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)
19 var numPages = 5;
21 $.widget( "ui.slider", $.ui.mouse, {
23         widgetEventPrefix: "slide",
25         options: {
26                 animate: false,
27                 distance: 0,
28                 max: 100,
29                 min: 0,
30                 orientation: "horizontal",
31                 range: false,
32                 step: 1,
33                 value: 0,
34                 values: null
35         },
37         _create: function() {
38                 var self = this,
39                         o = this.options,
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,
43                         handles = [];
45                 this._keySliding = false;
46                 this._mouseSliding = false;
47                 this._animateOff = true;
48                 this._handleIndex = null;
49                 this._detectOrientation();
50                 this._mouseInit();
52                 this.element
53                         .addClass( "ui-slider" +
54                                 " ui-slider-" + this.orientation +
55                                 " ui-widget" +
56                                 " ui-widget-content" +
57                                 " ui-corner-all" +
58                                 ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
60                 this.range = $([]);
62                 if ( o.range ) {
63                         if ( o.range === true ) {
64                                 if ( !o.values ) {
65                                         o.values = [ this._valueMin(), this._valueMin() ];
66                                 }
67                                 if ( o.values.length && o.values.length !== 2 ) {
68                                         o.values = [ o.values[0], o.values[0] ];
69                                 }
70                         }
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
77                                 " ui-widget-header" + 
78                                 ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
79                 }
81                 for ( var i = existingHandles.length; i < handleCount; i += 1 ) {
82                         handles.push( handle );
83                 }
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();
92                         })
93                         .hover(function() {
94                                 if ( !o.disabled ) {
95                                         $( this ).addClass( "ui-state-hover" );
96                                 }
97                         }, function() {
98                                 $( this ).removeClass( "ui-state-hover" );
99                         })
100                         .focus(function() {
101                                 if ( !o.disabled ) {
102                                         $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
103                                         $( this ).addClass( "ui-state-focus" );
104                                 } else {
105                                         $( this ).blur();
106                                 }
107                         })
108                         .blur(function() {
109                                 $( this ).removeClass( "ui-state-focus" );
110                         });
112                 this.handles.each(function( i ) {
113                         $( this ).data( "index.ui-slider-handle", i );
114                 });
116                 this.handles
117                         .keydown(function( event ) {
118                                 var index = $( this ).data( "index.ui-slider-handle" ),
119                                         allowed,
120                                         curVal,
121                                         newVal,
122                                         step;
123         
124                                 if ( self.options.disabled ) {
125                                         return;
126                                 }
127         
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 ) {
143                                                                 return;
144                                                         }
145                                                 }
146                                                 break;
147                                 }
148         
149                                 step = self.options.step;
150                                 if ( self.options.values && self.options.values.length ) {
151                                         curVal = newVal = self.values( index );
152                                 } else {
153                                         curVal = newVal = self.value();
154                                 }
155         
156                                 switch ( event.keyCode ) {
157                                         case $.ui.keyCode.HOME:
158                                                 newVal = self._valueMin();
159                                                 break;
160                                         case $.ui.keyCode.END:
161                                                 newVal = self._valueMax();
162                                                 break;
163                                         case $.ui.keyCode.PAGE_UP:
164                                                 newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) );
165                                                 break;
166                                         case $.ui.keyCode.PAGE_DOWN:
167                                                 newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) );
168                                                 break;
169                                         case $.ui.keyCode.UP:
170                                         case $.ui.keyCode.RIGHT:
171                                                 if ( curVal === self._valueMax() ) {
172                                                         return;
173                                                 }
174                                                 newVal = self._trimAlignValue( curVal + step );
175                                                 break;
176                                         case $.ui.keyCode.DOWN:
177                                         case $.ui.keyCode.LEFT:
178                                                 if ( curVal === self._valueMin() ) {
179                                                         return;
180                                                 }
181                                                 newVal = self._trimAlignValue( curVal - step );
182                                                 break;
183                                 }
184         
185                                 self._slide( event, index, newVal );
186                         })
187                         .keyup(function( event ) {
188                                 var index = $( this ).data( "index.ui-slider-handle" );
189         
190                                 if ( self._keySliding ) {
191                                         self._keySliding = false;
192                                         self._stop( event, index );
193                                         self._change( event, index );
194                                         $( this ).removeClass( "ui-state-active" );
195                                 }
196         
197                         });
199                 this._refreshValue();
201                 this._animateOff = false;
202         },
204         destroy: function() {
205                 this.handles.remove();
206                 this.range.remove();
208                 this.element
209                         .removeClass( "ui-slider" +
210                                 " ui-slider-horizontal" +
211                                 " ui-slider-vertical" +
212                                 " ui-slider-disabled" +
213                                 " ui-widget" +
214                                 " ui-widget-content" +
215                                 " ui-corner-all" )
216                         .removeData( "slider" )
217                         .unbind( ".slider" );
219                 this._mouseDestroy();
221                 return this;
222         },
224         _mouseCapture: function( event ) {
225                 var o = this.options,
226                         position,
227                         normValue,
228                         distance,
229                         closestHandle,
230                         self,
231                         index,
232                         allowed,
233                         offset,
234                         mouseOverHandle;
236                 if ( o.disabled ) {
237                         return false;
238                 }
240                 this.elementSize = {
241                         width: this.element.outerWidth(),
242                         height: this.element.outerHeight()
243                 };
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;
249                 self = this;
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 );
255                                 index = i;
256                         }
257                 });
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 ) {
263                         index += 1;
264                         closestHandle = $( this.handles[index] );
265                 }
267                 allowed = this._start( event, index );
268                 if ( allowed === false ) {
269                         return false;
270                 }
271                 this._mouseSliding = true;
273                 self._handleIndex = index;
275                 closestHandle
276                         .addClass( "ui-state-active" )
277                         .focus();
278                 
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)
288                 };
290                 if ( !this.handles.hasClass( "ui-state-hover" ) ) {
291                         this._slide( event, index, normValue );
292                 }
293                 this._animateOff = true;
294                 return true;
295         },
297         _mouseStart: function( event ) {
298                 return true;
299         },
301         _mouseDrag: function( event ) {
302                 var position = { x: event.pageX, y: event.pageY },
303                         normValue = this._normValueFromMouse( position );
304                 
305                 this._slide( event, this._handleIndex, normValue );
307                 return false;
308         },
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;
321                 return false;
322         },
323         
324         _detectOrientation: function() {
325                 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
326         },
328         _normValueFromMouse: function( position ) {
329                 var pixelTotal,
330                         pixelMouse,
331                         percentMouse,
332                         valueTotal,
333                         valueMouse;
335                 if ( this.orientation === "horizontal" ) {
336                         pixelTotal = this.elementSize.width;
337                         pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
338                 } else {
339                         pixelTotal = this.elementSize.height;
340                         pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
341                 }
343                 percentMouse = ( pixelMouse / pixelTotal );
344                 if ( percentMouse > 1 ) {
345                         percentMouse = 1;
346                 }
347                 if ( percentMouse < 0 ) {
348                         percentMouse = 0;
349                 }
350                 if ( this.orientation === "vertical" ) {
351                         percentMouse = 1 - percentMouse;
352                 }
354                 valueTotal = this._valueMax() - this._valueMin();
355                 valueMouse = this._valueMin() + percentMouse * valueTotal;
357                 return this._trimAlignValue( valueMouse );
358         },
360         _start: function( event, index ) {
361                 var uiHash = {
362                         handle: this.handles[ index ],
363                         value: this.value()
364                 };
365                 if ( this.options.values && this.options.values.length ) {
366                         uiHash.value = this.values( index );
367                         uiHash.values = this.values();
368                 }
369                 return this._trigger( "start", event, uiHash );
370         },
372         _slide: function( event, index, newVal ) {
373                 var otherVal,
374                         newValues,
375                         allowed;
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 ) )
382                                 ) {
383                                 newVal = otherVal;
384                         }
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 ],
392                                         value: newVal,
393                                         values: newValues
394                                 } );
395                                 otherVal = this.values( index ? 0 : 1 );
396                                 if ( allowed !== false ) {
397                                         this.values( index, newVal, true );
398                                 }
399                         }
400                 } else {
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 ],
405                                         value: newVal
406                                 } );
407                                 if ( allowed !== false ) {
408                                         this.value( newVal );
409                                 }
410                         }
411                 }
412         },
414         _stop: function( event, index ) {
415                 var uiHash = {
416                         handle: this.handles[ index ],
417                         value: this.value()
418                 };
419                 if ( this.options.values && this.options.values.length ) {
420                         uiHash.value = this.values( index );
421                         uiHash.values = this.values();
422                 }
424                 this._trigger( "stop", event, uiHash );
425         },
427         _change: function( event, index ) {
428                 if ( !this._keySliding && !this._mouseSliding ) {
429                         var uiHash = {
430                                 handle: this.handles[ index ],
431                                 value: this.value()
432                         };
433                         if ( this.options.values && this.options.values.length ) {
434                                 uiHash.value = this.values( index );
435                                 uiHash.values = this.values();
436                         }
438                         this._trigger( "change", event, uiHash );
439                 }
440         },
442         value: function( newValue ) {
443                 if ( arguments.length ) {
444                         this.options.value = this._trimAlignValue( newValue );
445                         this._refreshValue();
446                         this._change( null, 0 );
447                         return;
448                 }
450                 return this._value();
451         },
453         values: function( index, newValue ) {
454                 var vals,
455                         newValues,
456                         i;
458                 if ( arguments.length > 1 ) {
459                         this.options.values[ index ] = this._trimAlignValue( newValue );
460                         this._refreshValue();
461                         this._change( null, index );
462                         return;
463                 }
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 );
472                                 }
473                                 this._refreshValue();
474                         } else {
475                                 if ( this.options.values && this.options.values.length ) {
476                                         return this._values( index );
477                                 } else {
478                                         return this.value();
479                                 }
480                         }
481                 } else {
482                         return this._values();
483                 }
484         },
486         _setOption: function( key, value ) {
487                 var i,
488                         valsLength = 0;
490                 if ( $.isArray( this.options.values ) ) {
491                         valsLength = this.options.values.length;
492                 }
494                 $.Widget.prototype._setOption.apply( this, arguments );
496                 switch ( key ) {
497                         case "disabled":
498                                 if ( value ) {
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" );
503                                 } else {
504                                         this.handles.propAttr( "disabled", false );
505                                         this.element.removeClass( "ui-disabled" );
506                                 }
507                                 break;
508                         case "orientation":
509                                 this._detectOrientation();
510                                 this.element
511                                         .removeClass( "ui-slider-horizontal ui-slider-vertical" )
512                                         .addClass( "ui-slider-" + this.orientation );
513                                 this._refreshValue();
514                                 break;
515                         case "value":
516                                 this._animateOff = true;
517                                 this._refreshValue();
518                                 this._change( null, 0 );
519                                 this._animateOff = false;
520                                 break;
521                         case "values":
522                                 this._animateOff = true;
523                                 this._refreshValue();
524                                 for ( i = 0; i < valsLength; i += 1 ) {
525                                         this._change( null, i );
526                                 }
527                                 this._animateOff = false;
528                                 break;
529                 }
530         },
532         //internal value getter
533         // _value() returns value trimmed by min and max, aligned by step
534         _value: function() {
535                 var val = this.options.value;
536                 val = this._trimAlignValue( val );
538                 return val;
539         },
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 ) {
545                 var val,
546                         vals,
547                         i;
549                 if ( arguments.length ) {
550                         val = this.options.values[ index ];
551                         val = this._trimAlignValue( val );
553                         return val;
554                 } else {
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 ] );
560                         }
562                         return vals;
563                 }
564         },
565         
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();
570                 }
571                 if ( val >= this._valueMax() ) {
572                         return this._valueMax();
573                 }
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 );
580                 }
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) );
585         },
587         _valueMin: function() {
588                 return this.options.min;
589         },
591         _valueMax: function() {
592                 return this.options.max;
593         },
594         
595         _refreshValue: function() {
596                 var oRange = this.options.range,
597                         o = this.options,
598                         self = this,
599                         animate = ( !this._animateOff ) ? o.animate : false,
600                         valPercent,
601                         _set = {},
602                         lastValPercent,
603                         value,
604                         valueMin,
605                         valueMax;
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" ) {
614                                                 if ( i === 0 ) {
615                                                         self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
616                                                 }
617                                                 if ( i === 1 ) {
618                                                         self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
619                                                 }
620                                         } else {
621                                                 if ( i === 0 ) {
622                                                         self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
623                                                 }
624                                                 if ( i === 1 ) {
625                                                         self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
626                                                 }
627                                         }
628                                 }
629                                 lastValPercent = valPercent;
630                         });
631                 } else {
632                         value = this.value();
633                         valueMin = this._valueMin();
634                         valueMax = this._valueMax();
635                         valPercent = ( valueMax !== valueMin ) ?
636                                         ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
637                                         0;
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 );
643                         }
644                         if ( oRange === "max" && this.orientation === "horizontal" ) {
645                                 this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
646                         }
647                         if ( oRange === "min" && this.orientation === "vertical" ) {
648                                 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
649                         }
650                         if ( oRange === "max" && this.orientation === "vertical" ) {
651                                 this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
652                         }
653                 }
654         }
658 $.extend( $.ui.slider, {
659         version: "1.8.24"
662 }(jQuery));