Merge "Added release notes for 'ContentHandler::runLegacyHooks' removal"
[mediawiki.git] / resources / lib / jquery.ui / jquery.ui.slider.js
blobc3daa7a409c522e7e87b149d30c33e08a55be73e
1 /*!
2  * jQuery UI Slider 1.9.2
3  * http://jqueryui.com
4  *
5  * Copyright 2012 jQuery Foundation and other contributors
6  * Released under the MIT license.
7  * http://jquery.org/license
8  *
9  * http://api.jqueryui.com/slider/
10  *
11  * Depends:
12  *      jquery.ui.core.js
13  *      jquery.ui.mouse.js
14  *      jquery.ui.widget.js
15  */
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)
20 var numPages = 5;
22 $.widget( "ui.slider", $.ui.mouse, {
23         version: "1.9.2",
24         widgetEventPrefix: "slide",
26         options: {
27                 animate: false,
28                 distance: 0,
29                 max: 100,
30                 min: 0,
31                 orientation: "horizontal",
32                 range: false,
33                 step: 1,
34                 value: 0,
35                 values: null
36         },
38         _create: function() {
39                 var i, handleCount,
40                         o = this.options,
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                         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                 handleCount = ( o.values && o.values.length ) || 1;
83                 for ( i = existingHandles.length; i < handleCount; i++ ) {
84                         handles.push( handle );
85                 }
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();
94                         })
95                         .mouseenter(function() {
96                                 if ( !o.disabled ) {
97                                         $( this ).addClass( "ui-state-hover" );
98                                 }
99                         })
100                         .mouseleave(function() {
101                                 $( this ).removeClass( "ui-state-hover" );
102                         })
103                         .focus(function() {
104                                 if ( !o.disabled ) {
105                                         $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
106                                         $( this ).addClass( "ui-state-focus" );
107                                 } else {
108                                         $( this ).blur();
109                                 }
110                         })
111                         .blur(function() {
112                                 $( this ).removeClass( "ui-state-focus" );
113                         });
115                 this.handles.each(function( i ) {
116                         $( this ).data( "ui-slider-handle-index", i );
117                 });
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 ) {
139                                                                 return;
140                                                         }
141                                                 }
142                                                 break;
143                                 }
145                                 step = this.options.step;
146                                 if ( this.options.values && this.options.values.length ) {
147                                         curVal = newVal = this.values( index );
148                                 } else {
149                                         curVal = newVal = this.value();
150                                 }
152                                 switch ( event.keyCode ) {
153                                         case $.ui.keyCode.HOME:
154                                                 newVal = this._valueMin();
155                                                 break;
156                                         case $.ui.keyCode.END:
157                                                 newVal = this._valueMax();
158                                                 break;
159                                         case $.ui.keyCode.PAGE_UP:
160                                                 newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
161                                                 break;
162                                         case $.ui.keyCode.PAGE_DOWN:
163                                                 newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
164                                                 break;
165                                         case $.ui.keyCode.UP:
166                                         case $.ui.keyCode.RIGHT:
167                                                 if ( curVal === this._valueMax() ) {
168                                                         return;
169                                                 }
170                                                 newVal = this._trimAlignValue( curVal + step );
171                                                 break;
172                                         case $.ui.keyCode.DOWN:
173                                         case $.ui.keyCode.LEFT:
174                                                 if ( curVal === this._valueMin() ) {
175                                                         return;
176                                                 }
177                                                 newVal = this._trimAlignValue( curVal - step );
178                                                 break;
179                                 }
181                                 this._slide( event, index, newVal );
182                         },
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" );
191                                 }
192                         }
193                 });
195                 this._refreshValue();
197                 this._animateOff = false;
198         },
200         _destroy: function() {
201                 this.handles.remove();
202                 this.range.remove();
204                 this.element
205                         .removeClass( "ui-slider" +
206                                 " ui-slider-horizontal" +
207                                 " ui-slider-vertical" +
208                                 " ui-slider-disabled" +
209                                 " ui-widget" +
210                                 " ui-widget-content" +
211                                 " ui-corner-all" );
213                 this._mouseDestroy();
214         },
216         _mouseCapture: function( event ) {
217                 var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
218                         that = this,
219                         o = this.options;
221                 if ( o.disabled ) {
222                         return false;
223                 }
225                 this.elementSize = {
226                         width: this.element.outerWidth(),
227                         height: this.element.outerHeight()
228                 };
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 );
239                                 index = i;
240                         }
241                 });
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 ) {
247                         index += 1;
248                         closestHandle = $( this.handles[index] );
249                 }
251                 allowed = this._start( event, index );
252                 if ( allowed === false ) {
253                         return false;
254                 }
255                 this._mouseSliding = true;
257                 this._handleIndex = index;
259                 closestHandle
260                         .addClass( "ui-state-active" )
261                         .focus();
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)
272                 };
274                 if ( !this.handles.hasClass( "ui-state-hover" ) ) {
275                         this._slide( event, index, normValue );
276                 }
277                 this._animateOff = true;
278                 return true;
279         },
281         _mouseStart: function() {
282                 return true;
283         },
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 );
291                 return false;
292         },
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;
305                 return false;
306         },
308         _detectOrientation: function() {
309                 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
310         },
312         _normValueFromMouse: function( position ) {
313                 var pixelTotal,
314                         pixelMouse,
315                         percentMouse,
316                         valueTotal,
317                         valueMouse;
319                 if ( this.orientation === "horizontal" ) {
320                         pixelTotal = this.elementSize.width;
321                         pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
322                 } else {
323                         pixelTotal = this.elementSize.height;
324                         pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
325                 }
327                 percentMouse = ( pixelMouse / pixelTotal );
328                 if ( percentMouse > 1 ) {
329                         percentMouse = 1;
330                 }
331                 if ( percentMouse < 0 ) {
332                         percentMouse = 0;
333                 }
334                 if ( this.orientation === "vertical" ) {
335                         percentMouse = 1 - percentMouse;
336                 }
338                 valueTotal = this._valueMax() - this._valueMin();
339                 valueMouse = this._valueMin() + percentMouse * valueTotal;
341                 return this._trimAlignValue( valueMouse );
342         },
344         _start: function( event, index ) {
345                 var uiHash = {
346                         handle: this.handles[ index ],
347                         value: this.value()
348                 };
349                 if ( this.options.values && this.options.values.length ) {
350                         uiHash.value = this.values( index );
351                         uiHash.values = this.values();
352                 }
353                 return this._trigger( "start", event, uiHash );
354         },
356         _slide: function( event, index, newVal ) {
357                 var otherVal,
358                         newValues,
359                         allowed;
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 ) )
366                                 ) {
367                                 newVal = otherVal;
368                         }
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 ],
376                                         value: newVal,
377                                         values: newValues
378                                 } );
379                                 otherVal = this.values( index ? 0 : 1 );
380                                 if ( allowed !== false ) {
381                                         this.values( index, newVal, true );
382                                 }
383                         }
384                 } else {
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 ],
389                                         value: newVal
390                                 } );
391                                 if ( allowed !== false ) {
392                                         this.value( newVal );
393                                 }
394                         }
395                 }
396         },
398         _stop: function( event, index ) {
399                 var uiHash = {
400                         handle: this.handles[ index ],
401                         value: this.value()
402                 };
403                 if ( this.options.values && this.options.values.length ) {
404                         uiHash.value = this.values( index );
405                         uiHash.values = this.values();
406                 }
408                 this._trigger( "stop", event, uiHash );
409         },
411         _change: function( event, index ) {
412                 if ( !this._keySliding && !this._mouseSliding ) {
413                         var uiHash = {
414                                 handle: this.handles[ index ],
415                                 value: this.value()
416                         };
417                         if ( this.options.values && this.options.values.length ) {
418                                 uiHash.value = this.values( index );
419                                 uiHash.values = this.values();
420                         }
422                         this._trigger( "change", event, uiHash );
423                 }
424         },
426         value: function( newValue ) {
427                 if ( arguments.length ) {
428                         this.options.value = this._trimAlignValue( newValue );
429                         this._refreshValue();
430                         this._change( null, 0 );
431                         return;
432                 }
434                 return this._value();
435         },
437         values: function( index, newValue ) {
438                 var vals,
439                         newValues,
440                         i;
442                 if ( arguments.length > 1 ) {
443                         this.options.values[ index ] = this._trimAlignValue( newValue );
444                         this._refreshValue();
445                         this._change( null, index );
446                         return;
447                 }
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 );
456                                 }
457                                 this._refreshValue();
458                         } else {
459                                 if ( this.options.values && this.options.values.length ) {
460                                         return this._values( index );
461                                 } else {
462                                         return this.value();
463                                 }
464                         }
465                 } else {
466                         return this._values();
467                 }
468         },
470         _setOption: function( key, value ) {
471                 var i,
472                         valsLength = 0;
474                 if ( $.isArray( this.options.values ) ) {
475                         valsLength = this.options.values.length;
476                 }
478                 $.Widget.prototype._setOption.apply( this, arguments );
480                 switch ( key ) {
481                         case "disabled":
482                                 if ( value ) {
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" );
487                                 } else {
488                                         this.handles.prop( "disabled", false );
489                                         this.element.removeClass( "ui-disabled" );
490                                 }
491                                 break;
492                         case "orientation":
493                                 this._detectOrientation();
494                                 this.element
495                                         .removeClass( "ui-slider-horizontal ui-slider-vertical" )
496                                         .addClass( "ui-slider-" + this.orientation );
497                                 this._refreshValue();
498                                 break;
499                         case "value":
500                                 this._animateOff = true;
501                                 this._refreshValue();
502                                 this._change( null, 0 );
503                                 this._animateOff = false;
504                                 break;
505                         case "values":
506                                 this._animateOff = true;
507                                 this._refreshValue();
508                                 for ( i = 0; i < valsLength; i += 1 ) {
509                                         this._change( null, i );
510                                 }
511                                 this._animateOff = false;
512                                 break;
513                         case "min":
514                         case "max":
515                                 this._animateOff = true;
516                                 this._refreshValue();
517                                 this._animateOff = false;
518                                 break;
519                 }
520         },
522         //internal value getter
523         // _value() returns value trimmed by min and max, aligned by step
524         _value: function() {
525                 var val = this.options.value;
526                 val = this._trimAlignValue( val );
528                 return val;
529         },
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 ) {
535                 var val,
536                         vals,
537                         i;
539                 if ( arguments.length ) {
540                         val = this.options.values[ index ];
541                         val = this._trimAlignValue( val );
543                         return val;
544                 } else {
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 ] );
550                         }
552                         return vals;
553                 }
554         },
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();
560                 }
561                 if ( val >= this._valueMax() ) {
562                         return this._valueMax();
563                 }
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 );
570                 }
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) );
575         },
577         _valueMin: function() {
578                 return this.options.min;
579         },
581         _valueMax: function() {
582                 return this.options.max;
583         },
585         _refreshValue: function() {
586                 var lastValPercent, valPercent, value, valueMin, valueMax,
587                         oRange = this.options.range,
588                         o = this.options,
589                         that = this,
590                         animate = ( !this._animateOff ) ? o.animate : false,
591                         _set = {};
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" ) {
600                                                 if ( i === 0 ) {
601                                                         that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
602                                                 }
603                                                 if ( i === 1 ) {
604                                                         that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
605                                                 }
606                                         } else {
607                                                 if ( i === 0 ) {
608                                                         that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
609                                                 }
610                                                 if ( i === 1 ) {
611                                                         that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
612                                                 }
613                                         }
614                                 }
615                                 lastValPercent = valPercent;
616                         });
617                 } else {
618                         value = this.value();
619                         valueMin = this._valueMin();
620                         valueMax = this._valueMax();
621                         valPercent = ( valueMax !== valueMin ) ?
622                                         ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
623                                         0;
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 );
629                         }
630                         if ( oRange === "max" && this.orientation === "horizontal" ) {
631                                 this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
632                         }
633                         if ( oRange === "min" && this.orientation === "vertical" ) {
634                                 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
635                         }
636                         if ( oRange === "max" && this.orientation === "vertical" ) {
637                                 this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
638                         }
639                 }
640         }
644 }(jQuery));