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