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