# Yahoo! UI Library

### Slider Widget 2.5.0

Yahoo! UI Library > slider > DualSlider.js (source view)

Show Private Show Protected

    /**
     * A slider with two thumbs, one that represents the min value and
     * the other the max.  Actually a composition of two sliders, both with
     * the same background.  The constraints for each slider are adjusted
     * dynamically so that the min value of the max slider is equal or greater
     * to the current value of the min slider, and the max value of the min
     * slider is the current value of the max slider.
     * Constructor assumes both thumbs are positioned absolutely at the 0 mark on
     * the background.
     *
     * @namespace YAHOO.widget
     * @class DualSlider
     * @uses YAHOO.util.EventProvider
     * @constructor
     * @param {Slider} minSlider The Slider instance used for the min value thumb
     * @param {Slider} maxSlider The Slider instance used for the max value thumb
     * @param {int}    range The number of pixels the thumbs may move within
     * @param {Array}  initVals (optional) [min,max] Initial thumb placement
     */
    YAHOO.widget.DualSlider = function(minSlider, maxSlider, range, initVals) {

        var self = this,
            lang = YAHOO.lang;

        /**
         * A slider instance that keeps track of the lower value of the range.
         * <strong>read only</strong>
         * @property minSlider
         * @type Slider
         */
        this.minSlider = minSlider;

        /**
         * A slider instance that keeps track of the upper value of the range.
         * <strong>read only</strong>
         * @property maxSlider
         * @type Slider
         */
        this.maxSlider = maxSlider;

        /**
         * The currently active slider (min or max). <strong>read only</strong>
         * @property activeSlider
         * @type Slider
         */
        this.activeSlider = minSlider;

        /**
         * Is the DualSlider oriented horizontally or vertically?
         * <strong>read only</strong>
         * @property isHoriz
         * @type boolean
         */
        this.isHoriz = minSlider.thumb._isHoriz;

        // Validate initial values
        initVals = YAHOO.lang.isArray(initVals) ? initVals : [0,range];
        initVals[0] = Math.min(Math.max(parseInt(initVals[0],10)|0,0),range);
        initVals[1] = Math.max(Math.min(parseInt(initVals[1],10)|0,range),0);
        // Swap initVals if min > max
        if (initVals[0] > initVals[1]) {
            initVals.splice(0,2,initVals[1],initVals[0]);
        }

        var ready = { min : false, max : false };

        this.minSlider.thumb.onAvailable = function () {
            minSlider.setStartSliderState();
            ready.min = true;
            if (ready.max) {
                minSlider.setValue(initVals[0],true,true,true);
                maxSlider.setValue(initVals[1],true,true,true);
                self.updateValue(true);
                self.fireEvent('ready',self);
            }
        };
        this.maxSlider.thumb.onAvailable = function () {
            maxSlider.setStartSliderState();
            ready.max = true;
            if (ready.min) {
                minSlider.setValue(initVals[0],true,true,true);
                maxSlider.setValue(initVals[1],true,true,true);
                self.updateValue(true);
                self.fireEvent('ready',self);
            }
        };

        // dispatch mousedowns to the active slider
        minSlider.onMouseDown = function(e) {
            self._handleMouseDown(e);
        };

        // we can safely ignore a mousedown on one of the sliders since
        // they share a background
        maxSlider.onMouseDown = function(e) {
            YAHOO.util.Event.stopEvent(e);
        };

        // Fix the drag behavior so that only the active slider
        // follows the drag
        minSlider.onDrag =
        maxSlider.onDrag = function(e) {
            self._handleDrag(e);
        };

        // The core events for each slider are handled so we can expose a single
        // event for when the event happens on either slider
        minSlider.subscribe("change", this._handleMinChange, minSlider, this);
        minSlider.subscribe("slideStart", this._handleSlideStart, minSlider, this);
        minSlider.subscribe("slideEnd", this._handleSlideEnd, minSlider, this);

        maxSlider.subscribe("change", this._handleMaxChange, maxSlider, this);
        maxSlider.subscribe("slideStart", this._handleSlideStart, maxSlider, this);
        maxSlider.subscribe("slideEnd", this._handleSlideEnd, maxSlider, this);

        /**
         * Event that fires when the slider is finished setting up
         * @event ready
         * @param {DualSlider} dualslider the DualSlider instance
         */
        this.createEvent("ready", this);

        /**
         * Event that fires when either the min or max value changes
         * @event change
         * @param {DualSlider} dualslider the DualSlider instance
         */
        this.createEvent("change", this);

        /**
         * Event that fires when one of the thumbs begins to move
         * @event slideStart
         * @param {Slider} activeSlider the moving slider
         */
        this.createEvent("slideStart", this);

        /**
         * Event that fires when one of the thumbs finishes moving
         * @event slideEnd
         * @param {Slider} activeSlider the moving slider
         */
        this.createEvent("slideEnd", this);
    };

    YAHOO.widget.DualSlider.prototype = {

        /**
         * The current value of the min thumb. <strong>read only</strong>.
         * @property minVal
         * @type int
         */
        minVal : -1,

        /**
         * The current value of the max thumb. <strong>read only</strong>.
         * @property maxVal
         * @type int
         */
        maxVal : -1,

        /**
         * Pixel distance to maintain between thumbs.
         * @property minRange
         * @type int
         * @default 0
         */
        minRange : 0,

        /**
         * Executed when one of the sliders fires the slideStart event
         * @method _handleSlideStart
         * @private
         */
        _handleSlideStart: function(data, slider) {
            this.fireEvent("slideStart", slider);
        },

        /**
         * Executed when one of the sliders fires the slideEnd event
         * @method _handleSlideEnd
         * @private
         */
        _handleSlideEnd: function(data, slider) {
            this.fireEvent("slideEnd", slider);
        },

        /**
         * Overrides the onDrag method for both sliders
         * @method _handleDrag
         * @private
         */
        _handleDrag: function(e) {
            YAHOO.widget.Slider.prototype.onDrag.call(this.activeSlider, e);
        },

        /**
         * Executed when the min slider fires the change event
         * @method _handleMinChange
         * @private
         */
        _handleMinChange: function() {
            this.activeSlider = this.minSlider;
            this.updateValue();
        },

        /**
         * Executed when the max slider fires the change event
         * @method _handleMaxChange
         * @private
         */
        _handleMaxChange: function() {
            this.activeSlider = this.maxSlider;
            this.updateValue();
        },

        /**
         * Sets the min and max thumbs to new values.
         * @method setValues
         * @param min {int} Pixel offset to assign to the min thumb
         * @param max {int} Pixel offset to assign to the max thumb
         * @param skipAnim {boolean} (optional) Set to true to skip thumb animation.
         * Default false
         * @param force {boolean} (optional) ignore the locked setting and set
         * value anyway. Default false
         * @param silent {boolean} (optional) Set to true to skip firing change
         * events.  Default false
         */
        setValues : function (min, max, skipAnim, force, silent) {
            var mins = this.minSlider,
                maxs = this.maxSlider,
                mint = mins.thumb,
                maxt = maxs.thumb,
                self = this,
                done = { min : false, max : false };

            // Clear constraints to prevent animated thumbs from prematurely
            // stopping when hitting a constraint that's moving with the other
            // thumb.
            if (mint._isHoriz) {
                mint.setXConstraint(mint.leftConstraint,maxt.rightConstraint,mint.tickSize);
                maxt.setXConstraint(mint.leftConstraint,maxt.rightConstraint,maxt.tickSize);
            } else {
                mint.setYConstraint(mint.topConstraint,maxt.bottomConstraint,mint.tickSize);
                maxt.setYConstraint(mint.topConstraint,maxt.bottomConstraint,maxt.tickSize);
            }

            // Set up one-time slideEnd callbacks to call updateValue when both
            // thumbs have been set
            this._oneTimeCallback(mins,'slideEnd',function () {
                done.min = true;
                if (done.max) {
                    self.updateValue(silent);
                    // Clean the slider's slideEnd events on a timeout since this
                    // will be executed from inside the event's fire
                    setTimeout(function () {
                        self._cleanEvent(mins,'slideEnd');
                        self._cleanEvent(maxs,'slideEnd');
                    },0);
                }
            });

            this._oneTimeCallback(maxs,'slideEnd',function () {
                done.max = true;
                if (done.min) {
                    self.updateValue(silent);
                    // Clean both sliders' slideEnd events on a timeout since this
                    // will be executed from inside one of the event's fire
                    setTimeout(function () {
                        self._cleanEvent(mins,'slideEnd');
                        self._cleanEvent(maxs,'slideEnd');
                    },0);
                }
            });

            mins.setValue(min,skipAnim,force,silent);
            maxs.setValue(max,skipAnim,force,silent);
        },

        /**
         * Set the min thumb position to a new value.
         * @method setMinValue
         * @param min {int} Pixel offset for min thumb
         * @param skipAnim {boolean} (optional) Set to true to skip thumb animation.
         * Default false
         * @param force {boolean} (optional) ignore the locked setting and set
         * value anyway. Default false
         * @param silent {boolean} (optional) Set to true to skip firing change
         * events.  Default false
         */
        setMinValue : function (min, skipAnim, force, silent) {
            var mins = this.minSlider;

            this.activeSlider = mins;

            // Use a one-time event callback to delay the updateValue call
            // until after the slide operation is done
            var self = this;
            this._oneTimeCallback(mins,'slideEnd',function () {
                self.updateValue(silent);
                // Clean the slideEnd event on a timeout since this
                // will be executed from inside the event's fire
                setTimeout(function () { self._cleanEvent(mins,'slideEnd'); }, 0);
            });

            mins.setValue(min, skipAnim, force, silent);
        },

        /**
         * Set the max thumb position to a new value.
         * @method setMaxValue
         * @param max {int} Pixel offset for max thumb
         * @param skipAnim {boolean} (optional) Set to true to skip thumb animation.
         * Default false
         * @param force {boolean} (optional) ignore the locked setting and set
         * value anyway. Default false
         * @param silent {boolean} (optional) Set to true to skip firing change
         * events.  Default false
         */
        setMaxValue : function (max, skipAnim, force, silent) {
            var maxs = this.maxSlider;

            this.activeSlider = maxs;

            // Use a one-time event callback to delay the updateValue call
            // until after the slide operation is done
            var self = this;
            this._oneTimeCallback(maxs,'slideEnd',function () {
                self.updateValue(silent);
                // Clean the slideEnd event on a timeout since this
                // will be executed from inside the event's fire
                setTimeout(function () { self._cleanEvent(maxs,'slideEnd'); }, 0);
            });

            maxs.setValue(max, skipAnim, force, silent);
        },

        /**
         * Executed when one of the sliders is moved
         * @method updateValue
         * @param silent {boolean} (optional) Set to true to skip firing change
         * events.  Default false
         * @private
         */
        updateValue: function(silent) {
            var min     = this.minSlider.getValue(),
                max     = this.maxSlider.getValue(),
                changed = false;

            if (min != this.minVal || max != this.maxVal) {
                changed = true;

                var mint = this.minSlider.thumb;
                var maxt = this.maxSlider.thumb;

                var thumbInnerWidth = this.minSlider.thumbCenterPoint.x +
                                      this.maxSlider.thumbCenterPoint.x;

                // Establish barriers within the respective other thumb's edge, less
                // the minRange.  Limit to the Slider's range in the case of
                // negative minRanges.
                var minConstraint = Math.max(max-thumbInnerWidth-this.minRange,0);
                var maxConstraint = Math.min(-min-thumbInnerWidth-this.minRange,0);

                if (this.isHoriz) {
                    minConstraint = Math.min(minConstraint,maxt.rightConstraint);

                    mint.setXConstraint(mint.leftConstraint,minConstraint, mint.tickSize);

                    maxt.setXConstraint(maxConstraint,maxt.rightConstraint, maxt.tickSize);
                } else {
                    minConstraint = Math.min(minConstraint,maxt.bottomConstraint);
                    mint.setYConstraint(mint.leftConstraint,minConstraint, mint.tickSize);

                    maxt.setYConstraint(maxConstraint,maxt.bottomConstraint, maxt.tickSize);
                }
            }

            this.minVal = min;
            this.maxVal = max;

            if (changed && !silent) {
                this.fireEvent("change", this);
            }
        },

        /**
         * A background click will move the slider thumb nearest to the click.
         * Override if you need different behavior.
         * @method selectActiveSlider
         * @param e {Event} the mousedown event
         * @private
         */
        selectActiveSlider: function(e) {
            var min = this.minSlider.getValue(),
                max = this.maxSlider.getValue(),
                d;

            if (this.isHoriz) {
                d = YAHOO.util.Event.getPageX(e) - this.minSlider.initPageX -
                    this.minSlider.thumbCenterPoint.x;
            } else {
                d = YAHOO.util.Event.getPageY(e) - this.minSlider.initPageY -
                    this.minSlider.thumbCenterPoint.y;
            }

            // Below the minSlider thumb.  Move the minSlider thumb
            if (d < min) {
                this.activeSlider = this.minSlider;
            // Above the maxSlider thumb.  Move the maxSlider thumb
            } else if (d > max) {
                this.activeSlider = this.maxSlider;
            // Split the difference between thumbs
            } else {
                this.activeSlider = d*2 > max+min ? this.maxSlider : this.minSlider;
            }
        },

        /**
         * Overrides the onMouseDown for both slider, only moving the active slider
         * @method handleMouseDown
         * @private
         */
        _handleMouseDown: function(e) {
            this.selectActiveSlider(e);
            YAHOO.widget.Slider.prototype.onMouseDown.call(this.activeSlider, e);
        },

        /**
         * Schedule an event callback that will execute once, then unsubscribe
         * itself.
         * @method _oneTimeCallback
         * @param o {EventProvider} Object to attach the event to
         * @param evt {string} Name of the event
         * @param fn {Function} function to execute once
         * @private
         */
        _oneTimeCallback : function (o,evt,fn) {
            o.subscribe(evt,function () {
                // Unsubscribe myself
                o.unsubscribe(evt,arguments.callee);
                // Pass the event handler arguments to the one time callback
                fn.apply({},[].slice.apply(arguments));
            });
        },

        /**
         * Clean up the slideEnd event subscribers array, since each one-time
         * callback will be replaced in the event's subscribers property with
         * null.  This will cause memory bloat and loss of performance.
         * @method _cleanEvent
         * @param o {EventProvider} object housing the CustomEvent
         * @param evt {string} name of the CustomEvent
         * @private
         */
        _cleanEvent : function (o,evt) {
            if (o.__yui_events && o.events[evt]) {
                var ce, i, len;
                for (i = o.__yui_events.length; i >= 0; --i) {
                    if (o.__yui_events[i].type === evt) {
                        ce = o.__yui_events[i];
                        break;
                    }
                }
                if (ce) {
                    var subs    = ce.subscribers,
                        newSubs = [],
                        j = 0;
                    for (i = 0, len = subs.length; i < len; ++i) {
                        if (subs[i]) {
                            newSubs[j++] = subs[i];
                        }
                    }
                    ce.subscribers = newSubs;
                }
            }
        }

    };

    YAHOO.augment(YAHOO.widget.DualSlider, YAHOO.util.EventProvider);

    /**
     * Factory method for creating a horizontal dual-thumb slider
     * @for YAHOO.widget.Slider
     * @method YAHOO.widget.Slider.getHorizDualSlider
     * @static
     * @param {String} bg the id of the slider's background element
     * @param {String} minthumb the id of the min thumb
     * @param {String} maxthumb the id of the thumb thumb
     * @param {int} range the number of pixels the thumbs can move within
     * @param {int} iTickSize (optional) the element should move this many pixels
     * at a time
     * @param {Array}  initVals (optional) [min,max] Initial thumb placement
     * @return {DualSlider} a horizontal dual-thumb slider control
     */
    YAHOO.widget.Slider.getHorizDualSlider =
        function (bg, minthumb, maxthumb, range, iTickSize, initVals) {
            var mint, maxt;
            var YW = YAHOO.widget, Slider = YW.Slider, Thumb = YW.SliderThumb;

            mint = new Thumb(minthumb, bg, 0, range, 0, 0, iTickSize);
            maxt = new Thumb(maxthumb, bg, 0, range, 0, 0, iTickSize);

            return new YW.DualSlider(new Slider(bg, bg, mint, "horiz"), new Slider(bg, bg, maxt, "horiz"), range, initVals);
    };

    /**
     * Factory method for creating a vertical dual-thumb slider.
     * @for YAHOO.widget.Slider
     * @method YAHOO.widget.Slider.getVertDualSlider
     * @static
     * @param {String} bg the id of the slider's background element
     * @param {String} minthumb the id of the min thumb
     * @param {String} maxthumb the id of the thumb thumb
     * @param {int} range the number of pixels the thumbs can move within
     * @param {int} iTickSize (optional) the element should move this many pixels
     * at a time
     * @param {Array}  initVals (optional) [min,max] Initial thumb placement
     * @return {DualSlider} a vertical dual-thumb slider control
     */
    YAHOO.widget.Slider.getVertDualSlider =
        function (bg, minthumb, maxthumb, range, iTickSize, initVals) {
            var mint, maxt;
            var YW = YAHOO.widget, Slider = YW.Slider, Thumb = YW.SliderThumb;

            mint = new Thumb(minthumb, bg, 0, 0, 0, range, iTickSize);
            maxt = new Thumb(maxthumb, bg, 0, 0, 0, range, iTickSize);

            return new YW.DualSlider(new Slider(bg, bg, mint, "vert"), new Slider(bg, bg, maxt, "vert"), range, initVals);
    };

Copyright © 2007 Yahoo! Inc. All rights reserved.