Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / slider / Multi.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.slider.Multi
17  * @extends Ext.form.field.Base
18  * <p>Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis
19  * clicking and animation. Can be added as an item to any container. In addition,
20  * {@img Ext.slider.Multi/Ext.slider.Multi.png Ext.slider.Multi component}
21  * <p>Example usage:</p>
22  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
23 <pre>
24     Ext.create('Ext.slider.Multi', {
25         width: 200,
26         values: [25, 50, 75],
27         increment: 5,
28         minValue: 0,
29         maxValue: 100,
30
31         //this defaults to true, setting to false allows the thumbs to pass each other
32         {@link #constrainThumbs}: false,
33         renderTo: Ext.getBody()
34     });
35 </pre>
36  */
37 Ext.define('Ext.slider.Multi', {
38     extend: 'Ext.form.field.Base',
39     alias: 'widget.multislider',
40     alternateClassName: 'Ext.slider.MultiSlider',
41
42     requires: [
43         'Ext.slider.Thumb',
44         'Ext.slider.Tip',
45         'Ext.Number',
46         'Ext.util.Format',
47         'Ext.Template',
48         'Ext.layout.component.field.Slider'
49     ],
50
51     fieldSubTpl: [
52         '<div class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
53             '<div class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
54                 '<div class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
55                     '<a class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
56                 '</div>',
57             '</div>',
58         '</div>',
59         {
60             disableFormats: true,
61             compiled: true
62         }
63     ],
64
65     /**
66      * @cfg {Number} value
67      * A value with which to initialize the slider. Defaults to minValue. Setting this will only
68      * result in the creation of a single slider thumb; if you want multiple thumbs then use the
69      * {@link #values} config instead.
70      */
71
72     /**
73      * @cfg {Array} values
74      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for
75      * each value in this array. This will take precedence over the single {@link #value} config.
76      */
77
78     /**
79      * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
80      */
81     vertical: false,
82     /**
83      * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
84      */
85     minValue: 0,
86     /**
87      * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
88      */
89     maxValue: 100,
90     /**
91      * @cfg {Number/Boolean} decimalPrecision.
92      * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
93      * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
94      */
95     decimalPrecision: 0,
96     /**
97      * @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.
98      */
99     keyIncrement: 1,
100     /**
101      * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
102      */
103     increment: 0,
104
105     /**
106      * @private
107      * @property clickRange
108      * @type Array
109      * 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],
110      * 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'
111      * 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
112      */
113     clickRange: [5,15],
114
115     /**
116      * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
117      */
118     clickToChange : true,
119     /**
120      * @cfg {Boolean} animate Turn on or off animation. Defaults to true
121      */
122     animate: true,
123
124     /**
125      * True while the thumb is in a drag operation
126      * @type Boolean
127      */
128     dragging: false,
129
130     /**
131      * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
132      */
133     constrainThumbs: true,
134
135     componentLayout: 'sliderfield',
136
137     /**
138      * @cfg {Boolean} useTips
139      * True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
140      */
141     useTips : true,
142
143     /**
144      * @cfg {Function} tipText
145      * A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will
146      * use the default on the plugin.
147      */
148     tipText : null,
149
150     ariaRole: 'slider',
151
152     // private override
153     initValue: function() {
154         var me = this,
155             extValue = Ext.value,
156             // Fallback for initial values: values config -> value config -> minValue config -> 0
157             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
158             i = 0,
159             len = values.length;
160
161         // Store for use in dirty check
162         me.originalValue = values;
163
164         // Add a thumb for each value
165         for (; i < len; i++) {
166             me.addThumb(values[i]);
167         }
168     },
169
170     // private override
171     initComponent : function() {
172         var me = this,
173             tipPlug,
174             hasTip;
175
176         /**
177          * @property thumbs
178          * @type Array
179          * Array containing references to each thumb
180          */
181         me.thumbs = [];
182
183         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
184
185         me.addEvents(
186             /**
187              * @event beforechange
188              * Fires before the slider value is changed. By returning false from an event handler,
189              * you can cancel the event and prevent the slider from changing.
190              * @param {Ext.slider.Multi} slider The slider
191              * @param {Number} newValue The new value which the slider is being changed to.
192              * @param {Number} oldValue The old value which the slider was previously.
193              */
194             'beforechange',
195
196             /**
197              * @event change
198              * Fires when the slider value is changed.
199              * @param {Ext.slider.Multi} slider The slider
200              * @param {Number} newValue The new value which the slider has been changed to.
201              * @param {Ext.slider.Thumb} thumb The thumb that was changed
202              */
203             'change',
204
205             /**
206              * @event changecomplete
207              * Fires when the slider value is changed by the user and any drag operations have completed.
208              * @param {Ext.slider.Multi} slider The slider
209              * @param {Number} newValue The new value which the slider has been changed to.
210              * @param {Ext.slider.Thumb} thumb The thumb that was changed
211              */
212             'changecomplete',
213
214             /**
215              * @event dragstart
216              * Fires after a drag operation has started.
217              * @param {Ext.slider.Multi} slider The slider
218              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
219              */
220             'dragstart',
221
222             /**
223              * @event drag
224              * Fires continuously during the drag operation while the mouse is moving.
225              * @param {Ext.slider.Multi} slider The slider
226              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
227              */
228             'drag',
229
230             /**
231              * @event dragend
232              * Fires after the drag operation has completed.
233              * @param {Ext.slider.Multi} slider The slider
234              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
235              */
236             'dragend'
237         );
238
239         if (me.vertical) {
240             Ext.apply(me, Ext.slider.Multi.Vertical);
241         }
242
243         me.callParent();
244
245         // only can use it if it exists.
246         if (me.useTips) {
247             tipPlug = me.tipText ? {getText: me.tipText} : {};
248             me.plugins = me.plugins || [];
249             Ext.each(me.plugins, function(plug){
250                 if (plug.isSliderTip) {
251                     hasTip = true;
252                     return false;
253                 }
254             });
255             if (!hasTip) {
256                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
257             }
258         }
259     },
260
261     /**
262      * Creates a new thumb and adds it to the slider
263      * @param {Number} value The initial value to set on the thumb. Defaults to 0
264      * @return {Ext.slider.Thumb} The thumb
265      */
266     addThumb: function(value) {
267         var me = this,
268             thumb = Ext.create('Ext.slider.Thumb', {
269             value    : value,
270             slider   : me,
271             index    : me.thumbs.length,
272             constrain: me.constrainThumbs
273         });
274         me.thumbs.push(thumb);
275
276         //render the thumb now if needed
277         if (me.rendered) {
278             thumb.render();
279         }
280
281         return thumb;
282     },
283
284     /**
285      * @private
286      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
287      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
288      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
289      * range, which can result in the user not being able to move any of them.
290      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
291      */
292     promoteThumb: function(topThumb) {
293         var thumbs = this.thumbs,
294             ln = thumbs.length,
295             zIndex, thumb, i;
296
297         for (i = 0; i < ln; i++) {
298             thumb = thumbs[i];
299
300             if (thumb == topThumb) {
301                 thumb.bringToFront();
302             } else {
303                 thumb.sendToBack();
304             }
305         }
306     },
307
308     // private override
309     onRender : function() {
310         var me = this,
311             i = 0,
312             thumbs = me.thumbs,
313             len = thumbs.length,
314             thumb;
315
316         Ext.applyIf(me.subTplData, {
317             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
318             minValue: me.minValue,
319             maxValue: me.maxValue,
320             value: me.value
321         });
322
323         Ext.applyIf(me.renderSelectors, {
324             endEl: '.' + Ext.baseCSSPrefix + 'slider-end',
325             innerEl: '.' + Ext.baseCSSPrefix + 'slider-inner',
326             focusEl: '.' + Ext.baseCSSPrefix + 'slider-focus'
327         });
328
329         me.callParent(arguments);
330
331         //render each thumb
332         for (; i < len; i++) {
333             thumbs[i].render();
334         }
335
336         //calculate the size of half a thumb
337         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
338         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
339
340     },
341
342     /**
343      * Utility method to set the value of the field when the slider changes.
344      * @param {Object} slider The slider object.
345      * @param {Object} v The new value.
346      * @private
347      */
348     onChange : function(slider, v) {
349         this.setValue(v, undefined, true);
350     },
351
352     /**
353      * @private
354      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
355      */
356     initEvents : function() {
357         var me = this;
358
359         me.mon(me.el, {
360             scope    : me,
361             mousedown: me.onMouseDown,
362             keydown  : me.onKeyDown,
363             change : me.onChange
364         });
365
366         me.focusEl.swallowEvent("click", true);
367     },
368
369     /**
370      * @private
371      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
372      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
373      * @param {Ext.EventObject} e The click event
374      */
375     onMouseDown : function(e) {
376         var me = this,
377             thumbClicked = false,
378             i = 0,
379             thumbs = me.thumbs,
380             len = thumbs.length,
381             local;
382
383         if (me.disabled) {
384             return;
385         }
386
387         //see if the click was on any of the thumbs
388         for (; i < len; i++) {
389             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
390         }
391
392         if (me.clickToChange && !thumbClicked) {
393             local = me.innerEl.translatePoints(e.getXY());
394             me.onClickChange(local);
395         }
396         me.focus();
397     },
398
399     /**
400      * @private
401      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
402      * Only changes the value if the click was within this.clickRange.
403      * @param {Object} local Object containing top and left values for the click event.
404      */
405     onClickChange : function(local) {
406         var me = this,
407             thumb, index;
408
409         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
410             //find the nearest thumb to the click event
411             thumb = me.getNearest(local, 'left');
412             if (!thumb.disabled) {
413                 index = thumb.index;
414                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
415             }
416         }
417     },
418
419     /**
420      * @private
421      * Returns the nearest thumb to a click event, along with its distance
422      * @param {Object} local Object containing top and left values from a click event
423      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
424      * @return {Object} The closest thumb object and its distance from the click event
425      */
426     getNearest: function(local, prop) {
427         var me = this,
428             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
429             clickValue = me.reverseValue(localValue),
430             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
431             index = 0,
432             nearest = null,
433             thumbs = me.thumbs,
434             i = 0,
435             len = thumbs.length,
436             thumb,
437             value,
438             dist;
439
440         for (; i < len; i++) {
441             thumb = me.thumbs[i];
442             value = thumb.value;
443             dist  = Math.abs(value - clickValue);
444
445             if (Math.abs(dist <= nearestDistance)) {
446                 nearest = thumb;
447                 index = i;
448                 nearestDistance = dist;
449             }
450         }
451         return nearest;
452     },
453
454     /**
455      * @private
456      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
457      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
458      * @param {Ext.EventObject} e The Event object
459      */
460     onKeyDown : function(e) {
461         /*
462          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
463          * There's no real sane default for it, so leave it like this until we come up
464          * with a better way of doing it.
465          */
466         var me = this,
467             k,
468             val;
469
470         if(me.disabled || me.thumbs.length !== 1) {
471             e.preventDefault();
472             return;
473         }
474         k = e.getKey();
475
476         switch(k) {
477             case e.UP:
478             case e.RIGHT:
479                 e.stopEvent();
480                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
481                 me.setValue(0, val, undefined, true);
482             break;
483             case e.DOWN:
484             case e.LEFT:
485                 e.stopEvent();
486                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
487                 me.setValue(0, val, undefined, true);
488             break;
489             default:
490                 e.preventDefault();
491         }
492     },
493
494     // private
495     afterRender : function() {
496         var me = this,
497             i = 0,
498             thumbs = me.thumbs,
499             len = thumbs.length,
500             thumb,
501             v;
502
503         me.callParent(arguments);
504
505         for (; i < len; i++) {
506             thumb = thumbs[i];
507
508             if (thumb.value !== undefined) {
509                 v = me.normalizeValue(thumb.value);
510                 if (v !== thumb.value) {
511                     // delete this.value;
512                     me.setValue(i, v, false);
513                 } else {
514                     thumb.move(me.translateValue(v), false);
515                 }
516             }
517         }
518     },
519
520     /**
521      * @private
522      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
523      * the ratio is 2
524      * @return {Number} The ratio of pixels to mapped values
525      */
526     getRatio : function() {
527         var w = this.innerEl.getWidth(),
528             v = this.maxValue - this.minValue;
529         return v === 0 ? w : (w/v);
530     },
531
532     /**
533      * @private
534      * Returns a snapped, constrained value when given a desired value
535      * @param {Number} value Raw number value
536      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
537      */
538     normalizeValue : function(v) {
539         var me = this;
540
541         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
542         v = Ext.util.Format.round(v, me.decimalPrecision);
543         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
544         return v;
545     },
546
547     /**
548      * Sets the minimum value for the slider instance. If the current value is less than the
549      * minimum value, the current value will be changed.
550      * @param {Number} val The new minimum value
551      */
552     setMinValue : function(val) {
553         var me = this,
554             i = 0,
555             thumbs = me.thumbs,
556             len = thumbs.length,
557             t;
558
559         me.minValue = val;
560         me.inputEl.dom.setAttribute('aria-valuemin', val);
561
562         for (; i < len; ++i) {
563             t = thumbs[i];
564             t.value = t.value < val ? val : t.value;
565         }
566         me.syncThumbs();
567     },
568
569     /**
570      * Sets the maximum value for the slider instance. If the current value is more than the
571      * maximum value, the current value will be changed.
572      * @param {Number} val The new maximum value
573      */
574     setMaxValue : function(val) {
575         var me = this,
576             i = 0,
577             thumbs = me.thumbs,
578             len = thumbs.length,
579             t;
580
581         me.maxValue = val;
582         me.inputEl.dom.setAttribute('aria-valuemax', val);
583
584         for (; i < len; ++i) {
585             t = thumbs[i];
586             t.value = t.value > val ? val : t.value;
587         }
588         me.syncThumbs();
589     },
590
591     /**
592      * Programmatically sets the value of the Slider. Ensures that the value is constrained within
593      * the minValue and maxValue.
594      * @param {Number} index Index of the thumb to move
595      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
596      * @param {Boolean} animate Turn on or off animation, defaults to true
597      */
598     setValue : function(index, value, animate, changeComplete) {
599         var me = this,
600             thumb = me.thumbs[index];
601
602         // ensures value is contstrained and snapped
603         value = me.normalizeValue(value);
604
605         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
606             thumb.value = value;
607             if (me.rendered) {
608                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
609                 // Perhaps this should go on each thumb element rather than the outer element.
610                 me.inputEl.set({
611                     'aria-valuenow': value,
612                     'aria-valuetext': value
613                 });
614
615                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
616
617                 me.fireEvent('change', me, value, thumb);
618                 if (changeComplete) {
619                     me.fireEvent('changecomplete', me, value, thumb);
620                 }
621             }
622         }
623     },
624
625     /**
626      * @private
627      */
628     translateValue : function(v) {
629         var ratio = this.getRatio();
630         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
631     },
632
633     /**
634      * @private
635      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
636      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
637      * returns 200
638      * @param {Number} pos The position along the slider to return a mapped value for
639      * @return {Number} The mapped value for the given position
640      */
641     reverseValue : function(pos) {
642         var ratio = this.getRatio();
643         return (pos + (this.minValue * ratio)) / ratio;
644     },
645
646     // private
647     focus : function() {
648         this.focusEl.focus(10);
649     },
650
651     //private
652     onDisable: function() {
653         var me = this,
654             i = 0,
655             thumbs = me.thumbs,
656             len = thumbs.length,
657             thumb,
658             el,
659             xy;
660
661         me.callParent();
662
663         for (; i < len; i++) {
664             thumb = thumbs[i];
665             el = thumb.el;
666
667             thumb.disable();
668
669             if(Ext.isIE) {
670                 //IE breaks when using overflow visible and opacity other than 1.
671                 //Create a place holder for the thumb and display it.
672                 xy = el.getXY();
673                 el.hide();
674
675                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
676
677                 if (!me.thumbHolder) {
678                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
679                 }
680
681                 me.thumbHolder.show().setXY(xy);
682             }
683         }
684     },
685
686     //private
687     onEnable: function() {
688         var me = this,
689             i = 0,
690             thumbs = me.thumbs,
691             len = thumbs.length,
692             thumb,
693             el;
694
695         this.callParent();
696
697         for (; i < len; i++) {
698             thumb = thumbs[i];
699             el = thumb.el;
700
701             thumb.enable();
702
703             if (Ext.isIE) {
704                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
705
706                 if (me.thumbHolder) {
707                     me.thumbHolder.hide();
708                 }
709
710                 el.show();
711                 me.syncThumbs();
712             }
713         }
714     },
715
716     /**
717      * Synchronizes thumbs position to the proper proportion of the total component width based
718      * on the current slider {@link #value}.  This will be called automatically when the Slider
719      * is resized by a layout, but if it is rendered auto width, this method can be called from
720      * another resize handler to sync the Slider if necessary.
721      */
722     syncThumbs : function() {
723         if (this.rendered) {
724             var thumbs = this.thumbs,
725                 length = thumbs.length,
726                 i = 0;
727
728             for (; i < length; i++) {
729                 thumbs[i].move(this.translateValue(thumbs[i].value));
730             }
731         }
732     },
733
734     /**
735      * Returns the current value of the slider
736      * @param {Number} index The index of the thumb to return a value for
737      * @return {Number/Array} The current value of the slider at the given index, or an array of
738      * all thumb values if no index is given.
739      */
740     getValue : function(index) {
741         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
742     },
743
744     /**
745      * Returns an array of values - one for the location of each thumb
746      * @return {Array} The set of thumb values
747      */
748     getValues: function() {
749         var values = [],
750             i = 0,
751             thumbs = this.thumbs,
752             len = thumbs.length;
753
754         for (; i < len; i++) {
755             values.push(thumbs[i].value);
756         }
757
758         return values;
759     },
760
761     getSubmitValue: function() {
762         var me = this;
763         return (me.disabled || !me.submitValue) ? null : me.getValue();
764     },
765
766     reset: function() {
767         var me = this,
768             Array = Ext.Array;
769         Array.forEach(Array.from(me.originalValue), function(val, i) {
770             me.setValue(i, val);
771         });
772         me.clearInvalid();
773         // delete here so we reset back to the original state
774         delete me.wasValid;
775     },
776
777     // private
778     beforeDestroy : function() {
779         var me = this;
780
781         Ext.destroyMembers(me.innerEl, me.endEl, me.focusEl);
782         Ext.each(me.thumbs, function(thumb) {
783             Ext.destroy(thumb);
784         }, me);
785
786         me.callParent();
787     },
788
789     statics: {
790         // Method overrides to support slider with vertical orientation
791         Vertical: {
792             getRatio : function() {
793                 var h = this.innerEl.getHeight(),
794                     v = this.maxValue - this.minValue;
795                 return h/v;
796             },
797
798             onClickChange : function(local) {
799                 var me = this,
800                     thumb, index, bottom;
801
802                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
803                     thumb = me.getNearest(local, 'top');
804                     if (!thumb.disabled) {
805                         index = thumb.index;
806                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
807
808                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
809                     }
810                 }
811             }
812         }
813     }
814 });
815