# The source code

    /**
     * @class Ext.Slider
     * @extends Ext.BoxComponent
     * 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:

    new Ext.Slider({
        renderTo: Ext.getBody(),
        width: 200,
        value: 50,
        increment: 10,
        minValue: 0,
        maxValue: 100
    });

     */
    Ext.Slider = Ext.extend(Ext.BoxComponent, {
    	/**
    	 * @cfg {Number} value The value to initialize the slider with. Defaults to minValue.
    	 */
    	/**
    	 * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
    	 */
        vertical: false,
    	/**
    	 * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
    	 */
        minValue: 0,
    	/**
    	 * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
    	 */
        maxValue: 100,
        /**
         * @cfg {Number/Boolean} decimalPrecision.
         * The number of decimal places to which to round the Slider's value. Defaults to 0.
         * To disable rounding, configure as false.
         */
        decimalPrecision: 0,
    	/**
    	 * @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.
    	 */
        keyIncrement: 1,
    	/**
    	 * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
    	 */
        increment: 0,
    	// private
        clickRange: [5,15],
    	/**
    	 * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
    	 */
        clickToChange : true,
    	/**
    	 * @cfg {Boolean} animate Turn on or off animation. Defaults to true
    	 */
        animate: true,

        /**
         * True while the thumb is in a drag operation
         * @type boolean
         */
        dragging: false,

        // private override
        initComponent : function(){
            if(!Ext.isDefined(this.value)){
                this.value = this.minValue;
            }
            Ext.Slider.superclass.initComponent.call(this);
            this.keyIncrement = Math.max(this.increment, this.keyIncrement);
            this.addEvents(
                /**
                 * @event beforechange
                 * Fires before the slider value is changed. By returning false from an event handler,
                 * you can cancel the event and prevent the slider from changing.
    			 * @param {Ext.Slider} slider The slider
    			 * @param {Number} newValue The new value which the slider is being changed to.
    			 * @param {Number} oldValue The old value which the slider was previously.
                 */
    			'beforechange',
    			/**
    			 * @event change
    			 * Fires when the slider value is changed.
    			 * @param {Ext.Slider} slider The slider
    			 * @param {Number} newValue The new value which the slider has been changed to.
    			 */
    			'change',
    			/**
    			 * @event changecomplete
    			 * Fires when the slider value is changed by the user and any drag operations have completed.
    			 * @param {Ext.Slider} slider The slider
    			 * @param {Number} newValue The new value which the slider has been changed to.
    			 */
    			'changecomplete',
    			/**
    			 * @event dragstart
                 * Fires after a drag operation has started.
    			 * @param {Ext.Slider} slider The slider
    			 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
    			 */
    			'dragstart',
    			/**
    			 * @event drag
                 * Fires continuously during the drag operation while the mouse is moving.
    			 * @param {Ext.Slider} slider The slider
    			 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
    			 */
    			'drag',
    			/**
    			 * @event dragend
                 * Fires after the drag operation has completed.
    			 * @param {Ext.Slider} slider The slider
    			 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
    			 */
    			'dragend'
    		);

            if(this.vertical){
                Ext.apply(this, Ext.Slider.Vertical);
            }
        },

    	// private override
        onRender : function(){
            this.autoEl = {
                cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
                cn:{cls:'x-slider-end',cn:{cls:'x-slider-inner',cn:[{cls:'x-slider-thumb'},{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]}}
            };
            Ext.Slider.superclass.onRender.apply(this, arguments);
            this.endEl = this.el.first();
            this.innerEl = this.endEl.first();
            this.thumb = this.innerEl.first();
            this.halfThumb = (this.vertical ? this.thumb.getHeight() : this.thumb.getWidth())/2;
            this.focusEl = this.thumb.next();
            this.initEvents();
        },

    	// private override
        initEvents : function(){
            this.thumb.addClassOnOver('x-slider-thumb-over');
            this.mon(this.el, {
                scope: this,
                mousedown: this.onMouseDown,
                keydown: this.onKeyDown
            });

            this.focusEl.swallowEvent("click", true);

            this.tracker = new Ext.dd.DragTracker({
                onBeforeStart: this.onBeforeDragStart.createDelegate(this),
                onStart: this.onDragStart.createDelegate(this),
                onDrag: this.onDrag.createDelegate(this),
                onEnd: this.onDragEnd.createDelegate(this),
                tolerance: 3,
                autoStart: 300
            });
            this.tracker.initEl(this.thumb);
        },

    	// private override
        onMouseDown : function(e){
            if(this.disabled){
                return;
            }
            if(this.clickToChange && e.target != this.thumb.dom){
                var local = this.innerEl.translatePoints(e.getXY());
                this.onClickChange(local);
            }
            this.focus();
        },

    	// private
        onClickChange : function(local){
            if(local.top > this.clickRange[0] && local.top < this.clickRange[1]){
                this.setValue(Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
            }
        },

    	// private
        onKeyDown : function(e){
            if(this.disabled){e.preventDefault();return;}
            var k = e.getKey();
            switch(k){
                case e.UP:
                case e.RIGHT:
                    e.stopEvent();
                    if(e.ctrlKey){
                        this.setValue(this.maxValue, undefined, true);
                    }else{
                        this.setValue(this.value+this.keyIncrement, undefined, true);
                    }
                break;
                case e.DOWN:
                case e.LEFT:
                    e.stopEvent();
                    if(e.ctrlKey){
                        this.setValue(this.minValue, undefined, true);
                    }else{
                        this.setValue(this.value-this.keyIncrement, undefined, true);
                    }
                break;
                default:
                    e.preventDefault();
            }
        },

    	// private
        doSnap : function(value){
            if(!(this.increment && value)){
                return value;
            }
            var newValue = value,
                inc = this.increment,
                m = value % inc;
            if(m != 0){
                newValue -= m;
                if(m * 2 > inc){
                    newValue += inc;
                }else if(m * 2 < -inc){
                    newValue -= inc;
                }
            }
            return newValue.constrain(this.minValue,  this.maxValue);
        },

    	// private
        afterRender : function(){
            Ext.Slider.superclass.afterRender.apply(this, arguments);
            if(this.value !== undefined){
                var v = this.normalizeValue(this.value);
                if(v !== this.value){
                    delete this.value;
                    this.setValue(v, false);
                }else{
                    this.moveThumb(this.translateValue(v), false);
                }
            }
        },

    	// private
        getRatio : function(){
            var w = this.innerEl.getWidth(),
                v = this.maxValue - this.minValue;
            return v == 0 ? w : (w/v);
        },

    	// private
        normalizeValue : function(v){
            v = this.doSnap(v);
            v = Ext.util.Format.round(v, this.decimalPrecision);
            v = v.constrain(this.minValue, this.maxValue);
            return v;
        },

    	/**
    	 * Programmatically sets the value of the Slider. Ensures that the value is constrained within
    	 * the minValue and maxValue.
    	 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
    	 * @param {Boolean} animate Turn on or off animation, defaults to true
    	 */
        setValue : function(v, animate, changeComplete){
            v = this.normalizeValue(v);
            if(v !== this.value && this.fireEvent('beforechange', this, v, this.value) !== false){
                this.value = v;
                this.moveThumb(this.translateValue(v), animate !== false);
                this.fireEvent('change', this, v);
                if(changeComplete){
                    this.fireEvent('changecomplete', this, v);
                }
            }
        },

    	// private
        translateValue : function(v){
            var ratio = this.getRatio();
            return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
        },

    	reverseValue : function(pos){
            var ratio = this.getRatio();
            return (pos + this.halfThumb + (this.minValue * ratio)) / ratio;
        },

    	// private
        moveThumb: function(v, animate){
            if(!animate || this.animate === false){
                this.thumb.setLeft(v);
            }else{
                this.thumb.shift({left: v, stopFx: true, duration:.35});
            }
        },

    	// private
        focus : function(){
            this.focusEl.focus(10);
        },

    	// private
        onBeforeDragStart : function(e){
            return !this.disabled;
        },

    	// private
        onDragStart: function(e){
            this.thumb.addClass('x-slider-thumb-drag');
            this.dragging = true;
            this.dragStartValue = this.value;
            this.fireEvent('dragstart', this, e);
        },

    	// private
        onDrag: function(e){
            var pos = this.innerEl.translatePoints(this.tracker.getXY());
            this.setValue(Ext.util.Format.round(this.reverseValue(pos.left), this.decimalPrecision), false);
            this.fireEvent('drag', this, e);
        },

    	// private
        onDragEnd: function(e){
            this.thumb.removeClass('x-slider-thumb-drag');
            this.dragging = false;
            this.fireEvent('dragend', this, e);
            if(this.dragStartValue != this.value){
                this.fireEvent('changecomplete', this, this.value);
            }
        },

    	// private
        onResize : function(w, h){
            this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
            this.syncThumb();
        },

        //private
        onDisable: function(){
            Ext.Slider.superclass.onDisable.call(this);
            this.thumb.addClass(this.disabledClass);
            if(Ext.isIE){
                //IE breaks when using overflow visible and opacity other than 1.
                //Create a place holder for the thumb and display it.
                var xy = this.thumb.getXY();
                this.thumb.hide();
                this.innerEl.addClass(this.disabledClass).dom.disabled = true;
                if (!this.thumbHolder){
                    this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
                }
                this.thumbHolder.show().setXY(xy);
            }
        },

        //private
        onEnable: function(){
            Ext.Slider.superclass.onEnable.call(this);
            this.thumb.removeClass(this.disabledClass);
            if(Ext.isIE){
                this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
                if(this.thumbHolder){
                    this.thumbHolder.hide();
                }
                this.thumb.show();
                this.syncThumb();
            }
        },

        /**
         * Synchronizes the thumb position to the proper proportion of the total component width based
         * on the current slider {@link #value}.  This will be called automatically when the Slider
         * is resized by a layout, but if it is rendered auto width, this method can be called from
         * another resize handler to sync the Slider if necessary.
         */
        syncThumb : function(){
            if(this.rendered){
                this.moveThumb(this.translateValue(this.value));
            }
        },

    	/**
    	 * Returns the current value of the slider
    	 * @return {Number} The current value of the slider
    	 */
        getValue : function(){
            return this.value;
        },

        // private
        beforeDestroy : function(){
            Ext.destroyMembers(this, 'endEl', 'innerEl', 'thumb', 'halfThumb', 'focusEl', 'tracker', 'thumbHolder');
            Ext.Slider.superclass.beforeDestroy.call(this);
        }
    });
    Ext.reg('slider', Ext.Slider);

    // private class to support vertical sliders
    Ext.Slider.Vertical = {
        onResize : function(w, h){
            this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
            this.syncThumb();
        },

        getRatio : function(){
            var h = this.innerEl.getHeight(),
                v = this.maxValue - this.minValue;
            return h/v;
        },

        moveThumb: function(v, animate){
            if(!animate || this.animate === false){
                this.thumb.setBottom(v);
            }else{
                this.thumb.shift({bottom: v, stopFx: true, duration:.35});
            }
        },

        onDrag: function(e){
            var pos = this.innerEl.translatePoints(this.tracker.getXY()),
                bottom = this.innerEl.getHeight()-pos.top;
            this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), false);
            this.fireEvent('drag', this, e);
        },

        onClickChange : function(local){
            if(local.left > this.clickRange[0] && local.left < this.clickRange[1]){
                var bottom = this.innerEl.getHeight() - local.top;
                this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), undefined, true);
            }
        }
    };