Upgrade to ExtJS 3.2.2 - Released 06/02/2010
[extjs.git] / docs / source / Slider.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.2.2
11  * Copyright(c) 2006-2010 Ext JS, Inc.
12  * licensing@extjs.com
13  * http://www.extjs.com/license
14  */
15 Ext.ns('Ext.slider');
16
17 <div id="cls-Ext.slider.Thumb"></div>/**
18  * @class Ext.slider.Thumb
19  * @extends Object
20  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
21  * be created internally by an {@link Ext.slider.MultiSlider Ext.Slider}.
22  */
23 Ext.slider.Thumb = Ext.extend(Object, {
24
25     <div id="cfg-Ext.slider.Thumb-slider"></div>/**
26      * @constructor
27      * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required)
28      */
29     constructor: function(config) {
30         <div id="prop-Ext.slider.Thumb-slider"></div>/**
31          * @property slider
32          * @type Ext.slider.MultiSlider
33          * The slider this thumb is contained within
34          */
35         Ext.apply(this, config || {}, {
36             cls: 'x-slider-thumb',
37
38             <div id="cfg-Ext.slider.Thumb-constrain"></div>/**
39              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
40              */
41             constrain: false
42         });
43
44         Ext.slider.Thumb.superclass.constructor.call(this, config);
45
46         if (this.slider.vertical) {
47             Ext.apply(this, Ext.slider.Thumb.Vertical);
48         }
49     },
50
51     <div id="method-Ext.slider.Thumb-render"></div>/**
52      * Renders the thumb into a slider
53      */
54     render: function() {
55         this.el = this.slider.innerEl.insertFirst({cls: this.cls});
56
57         this.initEvents();
58     },
59
60     <div id="method-Ext.slider.Thumb-enable"></div>/**
61      * Enables the thumb if it is currently disabled
62      */
63     enable: function() {
64         this.disabled = false;
65         this.el.removeClass(this.slider.disabledClass);
66     },
67
68     <div id="method-Ext.slider.Thumb-disable"></div>/**
69      * Disables the thumb if it is currently enabled
70      */
71     disable: function() {
72         this.disabled = true;
73         this.el.addClass(this.slider.disabledClass);
74     },
75
76     <div id="method-Ext.slider.Thumb-initEvents"></div>/**
77      * Sets up an Ext.dd.DragTracker for this thumb
78      */
79     initEvents: function() {
80         var el = this.el;
81
82         el.addClassOnOver('x-slider-thumb-over');
83
84         this.tracker = new Ext.dd.DragTracker({
85             onBeforeStart: this.onBeforeDragStart.createDelegate(this),
86             onStart      : this.onDragStart.createDelegate(this),
87             onDrag       : this.onDrag.createDelegate(this),
88             onEnd        : this.onDragEnd.createDelegate(this),
89             tolerance    : 3,
90             autoStart    : 300
91         });
92
93         this.tracker.initEl(el);
94     },
95
96     /**
97      * @private
98      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
99      * this returns false to disable the DragTracker too.
100      * @return {Boolean} False if the slider is currently disabled
101      */
102     onBeforeDragStart : function(e) {
103         if (this.disabled) {
104             return false;
105         } else {
106             this.slider.promoteThumb(this);
107             return true;
108         }
109     },
110
111     /**
112      * @private
113      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
114      * to the thumb and fires the 'dragstart' event
115      */
116     onDragStart: function(e){
117         this.el.addClass('x-slider-thumb-drag');
118         this.dragging = true;
119         this.dragStartValue = this.value;
120
121         this.slider.fireEvent('dragstart', this.slider, e, this);
122     },
123
124     /**
125      * @private
126      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
127      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
128      */
129     onDrag: function(e) {
130         var slider   = this.slider,
131             index    = this.index,
132             newValue = this.getNewValue();
133
134         if (this.constrain) {
135             var above = slider.thumbs[index + 1],
136                 below = slider.thumbs[index - 1];
137
138             if (below != undefined && newValue <= below.value) newValue = below.value;
139             if (above != undefined && newValue >= above.value) newValue = above.value;
140         }
141
142         slider.setValue(index, newValue, false);
143         slider.fireEvent('drag', slider, e, this);
144     },
145
146     getNewValue: function() {
147         var slider   = this.slider,
148             pos      = slider.innerEl.translatePoints(this.tracker.getXY());
149
150         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
151     },
152
153     /**
154      * @private
155      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
156      * fires the 'changecomplete' event with the new value
157      */
158     onDragEnd: function(e) {
159         var slider = this.slider,
160             value  = this.value;
161
162         this.el.removeClass('x-slider-thumb-drag');
163
164         this.dragging = false;
165         slider.fireEvent('dragend', slider, e);
166
167         if (this.dragStartValue != value) {
168             slider.fireEvent('changecomplete', slider, value, this);
169         }
170     },
171     
172     /**
173      * @private
174      * Destroys the thumb
175      */
176     destroy: function(){
177         Ext.destroyMembers(this, 'tracker', 'el');
178     }
179 });
180
181 <div id="cls-Ext.slider.MultiSlider"></div>/**
182  * @class Ext.slider.MultiSlider
183  * @extends Ext.BoxComponent
184  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking and animation. Can be added as an item to any container. Example usage:
185 <pre>
186 new Ext.Slider({
187     renderTo: Ext.getBody(),
188     width: 200,
189     value: 50,
190     increment: 10,
191     minValue: 0,
192     maxValue: 100
193 });
194 </pre>
195  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
196 <pre>
197 new Ext.Slider({
198     renderTo: Ext.getBody(),
199     width: 200,
200     values: [25, 50, 75],
201     minValue: 0,
202     maxValue: 100,
203
204     //this defaults to true, setting to false allows the thumbs to pass each other
205     {@link #constrainThumbs}: false
206 });
207 </pre>
208  */
209 Ext.slider.MultiSlider = Ext.extend(Ext.BoxComponent, {
210     <div id="cfg-Ext.slider.MultiSlider-value"></div>/**
211      * @cfg {Number} value The value to initialize the slider with. Defaults to minValue.
212      */
213     <div id="cfg-Ext.slider.MultiSlider-vertical"></div>/**
214      * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
215      */
216     vertical: false,
217     <div id="cfg-Ext.slider.MultiSlider-minValue"></div>/**
218      * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
219      */
220     minValue: 0,
221     <div id="cfg-Ext.slider.MultiSlider-maxValue"></div>/**
222      * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
223      */
224     maxValue: 100,
225     <div id="cfg-Ext.slider.MultiSlider-decimalPrecision."></div>/**
226      * @cfg {Number/Boolean} decimalPrecision.
227      * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
228      * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
229      */
230     decimalPrecision: 0,
231     <div id="cfg-Ext.slider.MultiSlider-keyIncrement"></div>/**
232      * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead.
233      */
234     keyIncrement: 1,
235     <div id="cfg-Ext.slider.MultiSlider-increment"></div>/**
236      * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
237      */
238     increment: 0,
239
240     /**
241      * @private
242      * @property clickRange
243      * @type Array
244      * Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom],
245      * the click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top'
246      * value of the click event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range
247      */
248     clickRange: [5,15],
249
250     <div id="cfg-Ext.slider.MultiSlider-clickToChange"></div>/**
251      * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
252      */
253     clickToChange : true,
254     <div id="cfg-Ext.slider.MultiSlider-animate"></div>/**
255      * @cfg {Boolean} animate Turn on or off animation. Defaults to true
256      */
257     animate: true,
258
259     <div id="prop-Ext.slider.MultiSlider-dragging"></div>/**
260      * True while the thumb is in a drag operation
261      * @type Boolean
262      */
263     dragging: false,
264
265     <div id="cfg-Ext.slider.MultiSlider-constrainThumbs"></div>/**
266      * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
267      */
268     constrainThumbs: true,
269
270     /**
271      * @private
272      * @property topThumbZIndex
273      * @type Number
274      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
275      */
276     topThumbZIndex: 10000,
277
278     // private override
279     initComponent : function(){
280         if(!Ext.isDefined(this.value)){
281             this.value = this.minValue;
282         }
283
284         <div id="prop-Ext.slider.MultiSlider-thumbs"></div>/**
285          * @property thumbs
286          * @type Array
287          * Array containing references to each thumb
288          */
289         this.thumbs = [];
290
291         Ext.slider.MultiSlider.superclass.initComponent.call(this);
292
293         this.keyIncrement = Math.max(this.increment, this.keyIncrement);
294         this.addEvents(
295             <div id="event-Ext.slider.MultiSlider-beforechange"></div>/**
296              * @event beforechange
297              * Fires before the slider value is changed. By returning false from an event handler,
298              * you can cancel the event and prevent the slider from changing.
299              * @param {Ext.Slider} slider The slider
300              * @param {Number} newValue The new value which the slider is being changed to.
301              * @param {Number} oldValue The old value which the slider was previously.
302              */
303             'beforechange',
304
305             <div id="event-Ext.slider.MultiSlider-change"></div>/**
306              * @event change
307              * Fires when the slider value is changed.
308              * @param {Ext.Slider} slider The slider
309              * @param {Number} newValue The new value which the slider has been changed to.
310              * @param {Ext.slider.Thumb} thumb The thumb that was changed
311              */
312             'change',
313
314             <div id="event-Ext.slider.MultiSlider-changecomplete"></div>/**
315              * @event changecomplete
316              * Fires when the slider value is changed by the user and any drag operations have completed.
317              * @param {Ext.Slider} slider The slider
318              * @param {Number} newValue The new value which the slider has been changed to.
319              * @param {Ext.slider.Thumb} thumb The thumb that was changed
320              */
321             'changecomplete',
322
323             <div id="event-Ext.slider.MultiSlider-dragstart"></div>/**
324              * @event dragstart
325              * Fires after a drag operation has started.
326              * @param {Ext.Slider} slider The slider
327              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
328              */
329             'dragstart',
330
331             <div id="event-Ext.slider.MultiSlider-drag"></div>/**
332              * @event drag
333              * Fires continuously during the drag operation while the mouse is moving.
334              * @param {Ext.Slider} slider The slider
335              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
336              */
337             'drag',
338
339             <div id="event-Ext.slider.MultiSlider-dragend"></div>/**
340              * @event dragend
341              * Fires after the drag operation has completed.
342              * @param {Ext.Slider} slider The slider
343              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
344              */
345             'dragend'
346         );
347
348         <div id="prop-Ext.slider.MultiSlider-values"></div>/**
349          * @property values
350          * @type Array
351          * Array of values to initalize the thumbs with
352          */
353         if (this.values == undefined || Ext.isEmpty(this.values)) this.values = [0];
354
355         var values = this.values;
356
357         for (var i=0; i < values.length; i++) {
358             this.addThumb(values[i]);
359         }
360
361         if(this.vertical){
362             Ext.apply(this, Ext.slider.Vertical);
363         }
364     },
365
366     <div id="method-Ext.slider.MultiSlider-addThumb"></div>/**
367      * Creates a new thumb and adds it to the slider
368      * @param {Number} value The initial value to set on the thumb. Defaults to 0
369      */
370     addThumb: function(value) {
371         var thumb = new Ext.slider.Thumb({
372             value    : value,
373             slider   : this,
374             index    : this.thumbs.length,
375             constrain: this.constrainThumbs
376         });
377         this.thumbs.push(thumb);
378
379         //render the thumb now if needed
380         if (this.rendered) thumb.render();
381     },
382
383     /**
384      * @private
385      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
386      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
387      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
388      * range, which can result in the user not being able to move any of them.
389      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
390      */
391     promoteThumb: function(topThumb) {
392         var thumbs = this.thumbs,
393             zIndex, thumb;
394
395         for (var i = 0, j = thumbs.length; i < j; i++) {
396             thumb = thumbs[i];
397
398             if (thumb == topThumb) {
399                 zIndex = this.topThumbZIndex;
400             } else {
401                 zIndex = '';
402             }
403
404             thumb.el.setStyle('zIndex', zIndex);
405         }
406     },
407
408     // private override
409     onRender : function() {
410         this.autoEl = {
411             cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
412             cn : {
413                 cls: 'x-slider-end',
414                 cn : {
415                     cls:'x-slider-inner',
416                     cn : [{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]
417                 }
418             }
419         };
420
421         Ext.slider.MultiSlider.superclass.onRender.apply(this, arguments);
422
423         this.endEl   = this.el.first();
424         this.innerEl = this.endEl.first();
425         this.focusEl = this.innerEl.child('.x-slider-focus');
426
427         //render each thumb
428         for (var i=0; i < this.thumbs.length; i++) {
429             this.thumbs[i].render();
430         }
431
432         //calculate the size of half a thumb
433         var thumb      = this.innerEl.child('.x-slider-thumb');
434         this.halfThumb = (this.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
435
436         this.initEvents();
437     },
438
439     /**
440      * @private
441      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
442      * Creates a new DragTracker which is used to control what happens when the user drags the thumb around.
443      */
444     initEvents : function(){
445         this.mon(this.el, {
446             scope    : this,
447             mousedown: this.onMouseDown,
448             keydown  : this.onKeyDown
449         });
450
451         this.focusEl.swallowEvent("click", true);
452     },
453
454     /**
455      * @private
456      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
457      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
458      * @param {Ext.EventObject} e The click event
459      */
460     onMouseDown : function(e){
461         if(this.disabled){
462             return;
463         }
464
465         //see if the click was on any of the thumbs
466         var thumbClicked = false;
467         for (var i=0; i < this.thumbs.length; i++) {
468             thumbClicked = thumbClicked || e.target == this.thumbs[i].el.dom;
469         }
470
471         if (this.clickToChange && !thumbClicked) {
472             var local = this.innerEl.translatePoints(e.getXY());
473             this.onClickChange(local);
474         }
475         this.focus();
476     },
477
478     /**
479      * @private
480      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Vertical.
481      * Only changes the value if the click was within this.clickRange.
482      * @param {Object} local Object containing top and left values for the click event.
483      */
484     onClickChange : function(local) {
485         if (local.top > this.clickRange[0] && local.top < this.clickRange[1]) {
486             //find the nearest thumb to the click event
487             var thumb = this.getNearest(local, 'left'),
488                 index = thumb.index;
489
490             this.setValue(index, Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
491         }
492     },
493
494     /**
495      * @private
496      * Returns the nearest thumb to a click event, along with its distance
497      * @param {Object} local Object containing top and left values from a click event
498      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
499      * @return {Object} The closest thumb object and its distance from the click event
500      */
501     getNearest: function(local, prop) {
502         var localValue = prop == 'top' ? this.innerEl.getHeight() - local[prop] : local[prop],
503             clickValue = this.reverseValue(localValue),
504             nearestDistance = (this.maxValue - this.minValue) + 5, //add a small fudge for the end of the slider 
505             index = 0,
506             nearest = null;
507
508         for (var i=0; i < this.thumbs.length; i++) {
509             var thumb = this.thumbs[i],
510                 value = thumb.value,
511                 dist  = Math.abs(value - clickValue);
512
513             if (Math.abs(dist <= nearestDistance)) {
514                 nearest = thumb;
515                 index = i;
516                 nearestDistance = dist;
517             }
518         }
519         return nearest;
520     },
521
522     /**
523      * @private
524      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
525      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
526      * @param {Ext.EventObject} e The Event object
527      */
528     onKeyDown : function(e){
529         /*
530          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
531          * There's no real sane default for it, so leave it like this until we come up
532          * with a better way of doing it.
533          */
534         if(this.disabled || this.thumbs.length !== 1){
535             e.preventDefault();
536             return;
537         }
538         var k = e.getKey(),
539             val;
540         switch(k){
541             case e.UP:
542             case e.RIGHT:
543                 e.stopEvent();
544                 val = e.ctrlKey ? this.maxValue : this.getValue(0) + this.keyIncrement;
545                 this.setValue(0, val, undefined, true);
546             break;
547             case e.DOWN:
548             case e.LEFT:
549                 e.stopEvent();
550                 val = e.ctrlKey ? this.minValue : this.getValue(0) - this.keyIncrement;
551                 this.setValue(0, val, undefined, true);
552             break;
553             default:
554                 e.preventDefault();
555         }
556     },
557
558     /**
559      * @private
560      * If using snapping, this takes a desired new value and returns the closest snapped
561      * value to it
562      * @param {Number} value The unsnapped value
563      * @return {Number} The value of the nearest snap target
564      */
565     doSnap : function(value){
566         if (!(this.increment && value)) {
567             return value;
568         }
569         var newValue = value,
570             inc = this.increment,
571             m = value % inc;
572         if (m != 0) {
573             newValue -= m;
574             if (m * 2 >= inc) {
575                 newValue += inc;
576             } else if (m * 2 < -inc) {
577                 newValue -= inc;
578             }
579         }
580         return newValue.constrain(this.minValue,  this.maxValue);
581     },
582
583     // private
584     afterRender : function(){
585         Ext.slider.MultiSlider.superclass.afterRender.apply(this, arguments);
586
587         for (var i=0; i < this.thumbs.length; i++) {
588             var thumb = this.thumbs[i];
589
590             if (thumb.value !== undefined) {
591                 var v = this.normalizeValue(thumb.value);
592
593                 if (v !== thumb.value) {
594                     // delete this.value;
595                     this.setValue(i, v, false);
596                 } else {
597                     this.moveThumb(i, this.translateValue(v), false);
598                 }
599             }
600         };
601     },
602
603     /**
604      * @private
605      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
606      * the ratio is 2
607      * @return {Number} The ratio of pixels to mapped values
608      */
609     getRatio : function(){
610         var w = this.innerEl.getWidth(),
611             v = this.maxValue - this.minValue;
612         return v == 0 ? w : (w/v);
613     },
614
615     /**
616      * @private
617      * Returns a snapped, constrained value when given a desired value
618      * @param {Number} value Raw number value
619      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
620      */
621     normalizeValue : function(v){
622         v = this.doSnap(v);
623         v = Ext.util.Format.round(v, this.decimalPrecision);
624         v = v.constrain(this.minValue, this.maxValue);
625         return v;
626     },
627
628     <div id="method-Ext.slider.MultiSlider-setMinValue"></div>/**
629      * Sets the minimum value for the slider instance. If the current value is less than the
630      * minimum value, the current value will be changed.
631      * @param {Number} val The new minimum value
632      */
633     setMinValue : function(val){
634         this.minValue = val;
635         var i = 0,
636             thumbs = this.thumbs,
637             len = thumbs.length,
638             t;
639             
640         for(; i < len; ++i){
641             t = thumbs[i];
642             t.value = t.value < val ? val : t.value;
643         }
644         this.syncThumb();
645     },
646
647     <div id="method-Ext.slider.MultiSlider-setMaxValue"></div>/**
648      * Sets the maximum value for the slider instance. If the current value is more than the
649      * maximum value, the current value will be changed.
650      * @param {Number} val The new maximum value
651      */
652     setMaxValue : function(val){
653         this.maxValue = val;
654         var i = 0,
655             thumbs = this.thumbs,
656             len = thumbs.length,
657             t;
658             
659         for(; i < len; ++i){
660             t = thumbs[i];
661             t.value = t.value > val ? val : t.value;
662         }
663         this.syncThumb();
664     },
665
666     <div id="method-Ext.slider.MultiSlider-setValue"></div>/**
667      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
668      * the minValue and maxValue.
669      * @param {Number} index Index of the thumb to move
670      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
671      * @param {Boolean} animate Turn on or off animation, defaults to true
672      */
673     setValue : function(index, v, animate, changeComplete) {
674         var thumb = this.thumbs[index],
675             el    = thumb.el;
676
677         v = this.normalizeValue(v);
678
679         if (v !== thumb.value && this.fireEvent('beforechange', this, v, thumb.value, thumb) !== false) {
680             thumb.value = v;
681             if(this.rendered){
682                 this.moveThumb(index, this.translateValue(v), animate !== false);
683                 this.fireEvent('change', this, v, thumb);
684                 if(changeComplete){
685                     this.fireEvent('changecomplete', this, v, thumb);
686                 }
687             }
688         }
689     },
690
691     /**
692      * @private
693      */
694     translateValue : function(v) {
695         var ratio = this.getRatio();
696         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
697     },
698
699     /**
700      * @private
701      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
702      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
703      * returns 200
704      * @param {Number} pos The position along the slider to return a mapped value for
705      * @return {Number} The mapped value for the given position
706      */
707     reverseValue : function(pos){
708         var ratio = this.getRatio();
709         return (pos + (this.minValue * ratio)) / ratio;
710     },
711
712     /**
713      * @private
714      * @param {Number} index Index of the thumb to move
715      */
716     moveThumb: function(index, v, animate){
717         var thumb = this.thumbs[index].el;
718
719         if(!animate || this.animate === false){
720             thumb.setLeft(v);
721         }else{
722             thumb.shift({left: v, stopFx: true, duration:.35});
723         }
724     },
725
726     // private
727     focus : function(){
728         this.focusEl.focus(10);
729     },
730
731     // private
732     onResize : function(w, h){
733         var thumbs = this.thumbs,
734             len = thumbs.length,
735             i = 0;
736             
737         /*
738          * If we happen to be animating during a resize, the position of the thumb will likely be off
739          * when the animation stops. As such, just stop any animations before syncing the thumbs.
740          */
741         for(; i < len; ++i){
742             thumbs[i].el.stopFx();    
743         }
744         // check to see if we're using an auto width
745         if(Ext.isNumber(w)){
746             this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
747         }
748         this.syncThumb();
749         Ext.slider.MultiSlider.superclass.onResize.apply(this, arguments);
750     },
751
752     //private
753     onDisable: function(){
754         Ext.slider.MultiSlider.superclass.onDisable.call(this);
755
756         for (var i=0; i < this.thumbs.length; i++) {
757             var thumb = this.thumbs[i],
758                 el    = thumb.el;
759
760             thumb.disable();
761
762             if(Ext.isIE){
763                 //IE breaks when using overflow visible and opacity other than 1.
764                 //Create a place holder for the thumb and display it.
765                 var xy = el.getXY();
766                 el.hide();
767
768                 this.innerEl.addClass(this.disabledClass).dom.disabled = true;
769
770                 if (!this.thumbHolder) {
771                     this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
772                 }
773
774                 this.thumbHolder.show().setXY(xy);
775             }
776         }
777     },
778
779     //private
780     onEnable: function(){
781         Ext.slider.MultiSlider.superclass.onEnable.call(this);
782
783         for (var i=0; i < this.thumbs.length; i++) {
784             var thumb = this.thumbs[i],
785                 el    = thumb.el;
786
787             thumb.enable();
788
789             if (Ext.isIE) {
790                 this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
791
792                 if (this.thumbHolder) this.thumbHolder.hide();
793
794                 el.show();
795                 this.syncThumb();
796             }
797         }
798     },
799
800     <div id="method-Ext.slider.MultiSlider-syncThumb"></div>/**
801      * Synchronizes the thumb position to the proper proportion of the total component width based
802      * on the current slider {@link #value}.  This will be called automatically when the Slider
803      * is resized by a layout, but if it is rendered auto width, this method can be called from
804      * another resize handler to sync the Slider if necessary.
805      */
806     syncThumb : function() {
807         if (this.rendered) {
808             for (var i=0; i < this.thumbs.length; i++) {
809                 this.moveThumb(i, this.translateValue(this.thumbs[i].value));
810             }
811         }
812     },
813
814     <div id="method-Ext.slider.MultiSlider-getValue"></div>/**
815      * Returns the current value of the slider
816      * @param {Number} index The index of the thumb to return a value for
817      * @return {Number} The current value of the slider
818      */
819     getValue : function(index) {
820         return this.thumbs[index].value;
821     },
822
823     <div id="method-Ext.slider.MultiSlider-getValues"></div>/**
824      * Returns an array of values - one for the location of each thumb
825      * @return {Array} The set of thumb values
826      */
827     getValues: function() {
828         var values = [];
829
830         for (var i=0; i < this.thumbs.length; i++) {
831             values.push(this.thumbs[i].value);
832         }
833
834         return values;
835     },
836
837     // private
838     beforeDestroy : function(){
839         var thumbs = this.thumbs;
840         for(var i = 0, len = thumbs.length; i < len; ++i){
841             thumbs[i].destroy();
842             thumbs[i] = null;
843         }
844         Ext.destroyMembers(this, 'endEl', 'innerEl', 'focusEl', 'thumbHolder');
845         Ext.slider.MultiSlider.superclass.beforeDestroy.call(this);
846     }
847 });
848
849 Ext.reg('multislider', Ext.slider.MultiSlider);
850
851 <div id="cls-Ext.slider.SingleSlider"></div>/**
852  * @class Ext.slider.SingleSlider
853  * @extends Ext.slider.MultiSlider
854  * Slider which supports vertical or horizontal orientation, keyboard adjustments,
855  * configurable snapping, axis clicking and animation. Can be added as an item to
856  * any container. Example usage:
857 <pre><code>
858 new Ext.slider.SingleSlider({
859     renderTo: Ext.getBody(),
860     width: 200,
861     value: 50,
862     increment: 10,
863     minValue: 0,
864     maxValue: 100
865 });
866 </code></pre>
867  * The class Ext.slider.SingleSlider is aliased to Ext.Slider for backwards compatibility.
868  */
869 Ext.slider.SingleSlider = Ext.extend(Ext.slider.MultiSlider, {
870     constructor: function(config) {
871       config = config || {};
872
873       Ext.applyIf(config, {
874           values: [config.value || 0]
875       });
876
877       Ext.slider.SingleSlider.superclass.constructor.call(this, config);
878     },
879
880     <div id="method-Ext.slider.SingleSlider-getValue"></div>/**
881      * Returns the current value of the slider
882      * @return {Number} The current value of the slider
883      */
884     getValue: function() {
885         //just returns the value of the first thumb, which should be the only one in a single slider
886         return Ext.slider.SingleSlider.superclass.getValue.call(this, 0);
887     },
888
889     <div id="method-Ext.slider.SingleSlider-setValue"></div>/**
890      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
891      * the minValue and maxValue.
892      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
893      * @param {Boolean} animate Turn on or off animation, defaults to true
894      */
895     setValue: function(value, animate) {
896         var args = Ext.toArray(arguments),
897             len  = args.length;
898
899         //this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
900         //index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
901         //signature without the required index. The index will always be 0 for a single slider
902         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
903             args.unshift(0);
904         }
905
906         return Ext.slider.SingleSlider.superclass.setValue.apply(this, args);
907     },
908
909     <div id="method-Ext.slider.SingleSlider-syncThumb"></div>/**
910      * Synchronizes the thumb position to the proper proportion of the total component width based
911      * on the current slider {@link #value}.  This will be called automatically when the Slider
912      * is resized by a layout, but if it is rendered auto width, this method can be called from
913      * another resize handler to sync the Slider if necessary.
914      */
915     syncThumb : function() {
916         return Ext.slider.SingleSlider.superclass.syncThumb.apply(this, [0].concat(arguments));
917     },
918     
919     // private
920     getNearest : function(){
921         // Since there's only 1 thumb, it's always the nearest
922         return this.thumbs[0];    
923     }
924 });
925
926 //backwards compatibility
927 Ext.Slider = Ext.slider.SingleSlider;
928
929 Ext.reg('slider', Ext.slider.SingleSlider);
930
931 // private class to support vertical sliders
932 Ext.slider.Vertical = {
933     onResize : function(w, h){
934         this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
935         this.syncThumb();
936     },
937
938     getRatio : function(){
939         var h = this.innerEl.getHeight(),
940             v = this.maxValue - this.minValue;
941         return h/v;
942     },
943
944     moveThumb: function(index, v, animate) {
945         var thumb = this.thumbs[index],
946             el    = thumb.el;
947
948         if (!animate || this.animate === false) {
949             el.setBottom(v);
950         } else {
951             el.shift({bottom: v, stopFx: true, duration:.35});
952         }
953     },
954
955     onClickChange : function(local) {
956         if (local.left > this.clickRange[0] && local.left < this.clickRange[1]) {
957             var thumb = this.getNearest(local, 'top'),
958                 index = thumb.index,
959                 value = this.minValue + this.reverseValue(this.innerEl.getHeight() - local.top);
960
961             this.setValue(index, Ext.util.Format.round(value, this.decimalPrecision), undefined, true);
962         }
963     }
964 };
965
966 //private class to support vertical dragging of thumbs within a slider
967 Ext.slider.Thumb.Vertical = {
968     getNewValue: function() {
969         var slider   = this.slider,
970             innerEl  = slider.innerEl,
971             pos      = innerEl.translatePoints(this.tracker.getXY()),
972             bottom   = innerEl.getHeight() - pos.top;
973
974         return slider.minValue + Ext.util.Format.round(bottom / slider.getRatio(), slider.decimalPrecision);
975     }
976 };
977 </pre>    
978 </body>
979 </html>