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