/*
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.6.0
*/
/**
 * The Slider component is a UI control that enables the user to adjust 
 * values in a finite range along one or two axes. Typically, the Slider 
 * control is used in a web application as a rich, visual replacement 
 * for an input box that takes a number as input. The Slider control can 
 * also easily accommodate a second dimension, providing x,y output for 
 * a selection point chosen from a rectangular region.
 *
 * @module    slider
 * @title     Slider Widget
 * @namespace YAHOO.widget
 * @requires  yahoo,dom,dragdrop,event
 * @optional  animation
 */

/**
 * A DragDrop implementation that can be used as a background for a
 * slider.  It takes a reference to the thumb instance 
 * so it can delegate some of the events to it.  The goal is to make the 
 * thumb jump to the location on the background when the background is 
 * clicked.  
 *
 * @class Slider
 * @extends YAHOO.util.DragDrop
 * @uses YAHOO.util.EventProvider
 * @constructor
 * @param {String}      id     The id of the element linked to this instance
 * @param {String}      sGroup The group of related DragDrop items
 * @param {SliderThumb} oThumb The thumb for this slider
 * @param {String}      sType  The type of slider (horiz, vert, region)
 */
YAHOO.widget.Slider = function(sElementId, sGroup, oThumb, sType) {

    YAHOO.widget.Slider.ANIM_AVAIL = 
        (!YAHOO.lang.isUndefined(YAHOO.util.Anim));

    if (sElementId) {
        this.init(sElementId, sGroup, true);
        this.initSlider(sType);
        this.initThumb(oThumb);
    }
};

/**
 * Factory method for creating a horizontal slider
 * @method YAHOO.widget.Slider.getHorizSlider
 * @static
 * @param {String} sBGElId the id of the slider's background element
 * @param {String} sHandleElId the id of the thumb element
 * @param {int} iLeft the number of pixels the element can move left
 * @param {int} iRight the number of pixels the element can move right
 * @param {int} iTickSize optional parameter for specifying that the element 
 * should move a certain number pixels at a time.
 * @return {Slider} a horizontal slider control
 */
YAHOO.widget.Slider.getHorizSlider = 
    function (sBGElId, sHandleElId, iLeft, iRight, iTickSize) {
        return new YAHOO.widget.Slider(sBGElId, sBGElId, 
            new YAHOO.widget.SliderThumb(sHandleElId, sBGElId, 
                               iLeft, iRight, 0, 0, iTickSize), "horiz");
};

/**
 * Factory method for creating a vertical slider
 * @method YAHOO.widget.Slider.getVertSlider
 * @static
 * @param {String} sBGElId the id of the slider's background element
 * @param {String} sHandleElId the id of the thumb element
 * @param {int} iUp the number of pixels the element can move up
 * @param {int} iDown the number of pixels the element can move down
 * @param {int} iTickSize optional parameter for specifying that the element 
 * should move a certain number pixels at a time.
 * @return {Slider} a vertical slider control
 */
YAHOO.widget.Slider.getVertSlider = 
    function (sBGElId, sHandleElId, iUp, iDown, iTickSize) {
        return new YAHOO.widget.Slider(sBGElId, sBGElId, 
            new YAHOO.widget.SliderThumb(sHandleElId, sBGElId, 0, 0, 
                               iUp, iDown, iTickSize), "vert");
};

/**
 * Factory method for creating a slider region like the one in the color
 * picker example
 * @method YAHOO.widget.Slider.getSliderRegion
 * @static
 * @param {String} sBGElId the id of the slider's background element
 * @param {String} sHandleElId the id of the thumb element
 * @param {int} iLeft the number of pixels the element can move left
 * @param {int} iRight the number of pixels the element can move right
 * @param {int} iUp the number of pixels the element can move up
 * @param {int} iDown the number of pixels the element can move down
 * @param {int} iTickSize optional parameter for specifying that the element 
 * should move a certain number pixels at a time.
 * @return {Slider} a slider region control
 */
YAHOO.widget.Slider.getSliderRegion = 
    function (sBGElId, sHandleElId, iLeft, iRight, iUp, iDown, iTickSize) {
        return new YAHOO.widget.Slider(sBGElId, sBGElId, 
            new YAHOO.widget.SliderThumb(sHandleElId, sBGElId, iLeft, iRight, 
                               iUp, iDown, iTickSize), "region");
};

/**
 * By default, animation is available if the animation utility is detected.
 * @property YAHOO.widget.Slider.ANIM_AVAIL
 * @static
 * @type boolean
 */
YAHOO.widget.Slider.ANIM_AVAIL = false;

YAHOO.extend(YAHOO.widget.Slider, YAHOO.util.DragDrop, {

    /**
     * Override the default setting of dragOnly to true.
     * @property dragOnly
     * @type boolean
     * @default true
     */
    dragOnly : true,

    /**
     * Initializes the slider.  Executed in the constructor
     * @method initSlider
     * @param {string} sType the type of slider (horiz, vert, region)
     */
    initSlider: function(sType) {

        /**
         * The type of the slider (horiz, vert, region)
         * @property type
         * @type string
         */
        this.type = sType;

        //this.removeInvalidHandleType("A");


        /**
         * Event the fires when the value of the control changes.  If 
         * the control is animated the event will fire every point
         * along the way.
         * @event change
         * @param {int} newOffset|x the new offset for normal sliders, or the new
         *                          x offset for region sliders
         * @param {int} y the number of pixels the thumb has moved on the y axis
         *                (region sliders only)
         */
        this.createEvent("change", this);

        /**
         * Event that fires at the beginning of a slider thumb move.
         * @event slideStart
         */
        this.createEvent("slideStart", this);

        /**
         * Event that fires at the end of a slider thumb move
         * @event slideEnd
         */
        this.createEvent("slideEnd", this);

        /**
         * Overrides the isTarget property in YAHOO.util.DragDrop
         * @property isTarget
         * @private
         */
        this.isTarget = false;
    
        /**
         * Flag that determines if the thumb will animate when moved
         * @property animate
         * @type boolean
         */
        this.animate = YAHOO.widget.Slider.ANIM_AVAIL;

        /**
         * Set to false to disable a background click thumb move
         * @property backgroundEnabled
         * @type boolean
         */
        this.backgroundEnabled = true;

        /**
         * Adjustment factor for tick animation, the more ticks, the
         * faster the animation (by default)
         * @property tickPause
         * @type int
         */
        this.tickPause = 40;

        /**
         * Enables the arrow, home and end keys, defaults to true.
         * @property enableKeys
         * @type boolean
         */
        this.enableKeys = true;

        /**
         * Specifies the number of pixels the arrow keys will move the slider.
         * Default is 20.
         * @property keyIncrement
         * @type int
         */
        this.keyIncrement = 20;

        /**
         * moveComplete is set to true when the slider has moved to its final
         * destination.  For animated slider, this value can be checked in 
         * the onChange handler to make it possible to execute logic only
         * when the move is complete rather than at all points along the way.
         * Deprecated because this flag is only useful when the background is
         * clicked and the slider is animated.  If the user drags the thumb,
         * the flag is updated when the drag is over ... the final onDrag event
         * fires before the mouseup the ends the drag, so the implementer will
         * never see it.
         *
         * @property moveComplete
         * @type Boolean
         * @deprecated use the slideEnd event instead
         */
        this.moveComplete = true;

        /**
         * If animation is configured, specifies the length of the animation
         * in seconds.
         * @property animationDuration
         * @type int
         * @default 0.2
         */
        this.animationDuration = 0.2;

        /**
         * Constant for valueChangeSource, indicating that the user clicked or
         * dragged the slider to change the value.
         * @property SOURCE_UI_EVENT
         * @final
         * @default 1
         */
        this.SOURCE_UI_EVENT = 1;

        /**
         * Constant for valueChangeSource, indicating that the value was altered
         * by a programmatic call to setValue/setRegionValue.
         * @property SOURCE_SET_VALUE
         * @final
         * @default 2
         */
        this.SOURCE_SET_VALUE = 2;

        /**
         * When the slider value changes, this property is set to identify where
         * the update came from.  This will be either 1, meaning the slider was
         * clicked or dragged, or 2, meaning that it was set via a setValue() call.
         * This can be used within event handlers to apply some of the logic only
         * when dealing with one source or another.
         * @property valueChangeSource
         * @type int
         * @since 2.3.0
         */
        this.valueChangeSource = 0;

        /**
         * Indicates whether or not events will be supressed for the current
         * slide operation
         * @property _silent
         * @type boolean
         * @private
         */
        this._silent = false;

        /**
         * Saved offset used to protect against NaN problems when slider is
         * set to display:none
         * @property lastOffset
         * @type [int, int]
         */
        this.lastOffset = [0,0];
    },

    /**
     * Initializes the slider's thumb. Executed in the constructor.
     * @method initThumb
     * @param {YAHOO.widget.SliderThumb} t the slider thumb
     */
    initThumb: function(t) {

        var self = this;

        /**
         * A YAHOO.widget.SliderThumb instance that we will use to 
         * reposition the thumb when the background is clicked
         * @property thumb
         * @type YAHOO.widget.SliderThumb
         */
        this.thumb = t;
        t.cacheBetweenDrags = true;

        // add handler for the handle onchange event
        //t.onChange = function() { 
            //self.handleThumbChange(); 
        //};

        if (t._isHoriz && t.xTicks && t.xTicks.length) {
            this.tickPause = Math.round(360 / t.xTicks.length);
        } else if (t.yTicks && t.yTicks.length) {
            this.tickPause = Math.round(360 / t.yTicks.length);
        }


        // delegate thumb methods
        t.onAvailable = function() { 
                return self.setStartSliderState(); 
            };
        t.onMouseDown = function () { 
                return self.focus(); 
            };
        t.startDrag = function() { 
                self._slideStart(); 
            };
        t.onDrag = function() { 
                self.fireEvents(true); 
            };
        t.onMouseUp = function() { 
                self.thumbMouseUp(); 
            };

    },

    /**
     * Executed when the slider element is available
     * @method onAvailable
     */
    onAvailable: function() {
        var Event = YAHOO.util.Event;
        Event.on(this.id, "keydown",  this.handleKeyDown,  this, true);
        Event.on(this.id, "keypress", this.handleKeyPress, this, true);
    },
 
    /**
     * Executed when a keypress event happens with the control focused.
     * Prevents the default behavior for navigation keys.  The actual
     * logic for moving the slider thumb in response to a key event
     * happens in handleKeyDown.
     * @param {Event} e the keypress event
     */
    handleKeyPress: function(e) {
        if (this.enableKeys) {
            var Event = YAHOO.util.Event;
            var kc = Event.getCharCode(e);
            switch (kc) {
                case 0x25: // left
                case 0x26: // up
                case 0x27: // right
                case 0x28: // down
                case 0x24: // home
                case 0x23: // end
                    Event.preventDefault(e);
                    break;
                default:
            }
        }
    },

    /**
     * Executed when a keydown event happens with the control focused.
     * Updates the slider value and display when the keypress is an
     * arrow key, home, or end as long as enableKeys is set to true.
     * @param {Event} e the keydown event
     */
    handleKeyDown: function(e) {
        if (this.enableKeys) {
            var Event = YAHOO.util.Event;

            var kc = Event.getCharCode(e), t=this.thumb;
            var h=this.getXValue(),v=this.getYValue();

            var horiz = false;
            var changeValue = true;
            switch (kc) {

                // left
                case 0x25: h -= this.keyIncrement; break;

                // up
                case 0x26: v -= this.keyIncrement; break;

                // right
                case 0x27: h += this.keyIncrement; break;

                // down
                case 0x28: v += this.keyIncrement; break;

                // home
                case 0x24: h = t.leftConstraint;    
                           v = t.topConstraint;    
                           break;

                // end
                case 0x23: h = t.rightConstraint; 
                           v = t.bottomConstraint;    
                           break;

                default:   changeValue = false;
            }

            if (changeValue) {
                if (t._isRegion) {
                    this.setRegionValue(h, v, true);
                } else {
                    var newVal = (t._isHoriz) ? h : v;
                    this.setValue(newVal, true);
                }
                Event.stopEvent(e);
            }

        }
    },

    /**
     * Initialization that sets up the value offsets once the elements are ready
     * @method setStartSliderState
     */
    setStartSliderState: function() {


        this.setThumbCenterPoint();

        /**
         * The basline position of the background element, used
         * to determine if the background has moved since the last
         * operation.
         * @property baselinePos
         * @type [int, int]
         */
        this.baselinePos = YAHOO.util.Dom.getXY(this.getEl());

        this.thumb.startOffset = this.thumb.getOffsetFromParent(this.baselinePos);

        if (this.thumb._isRegion) {
            if (this.deferredSetRegionValue) {
                this.setRegionValue.apply(this, this.deferredSetRegionValue);
                this.deferredSetRegionValue = null;
            } else {
                this.setRegionValue(0, 0, true, true, true);
            }
        } else {
            if (this.deferredSetValue) {
                this.setValue.apply(this, this.deferredSetValue);
                this.deferredSetValue = null;
            } else {
                this.setValue(0, true, true, true);
            }
        }
    },

    /**
     * When the thumb is available, we cache the centerpoint of the element so
     * we can position the element correctly when the background is clicked
     * @method setThumbCenterPoint
     */
    setThumbCenterPoint: function() {

        var el = this.thumb.getEl();

        if (el) {
            /**
             * The center of the slider element is stored so we can 
             * place it in the correct position when the background is clicked.
             * @property thumbCenterPoint
             * @type {"x": int, "y": int}
             */
            this.thumbCenterPoint = { 
                    x: parseInt(el.offsetWidth/2, 10), 
                    y: parseInt(el.offsetHeight/2, 10) 
            };
        }

    },

    /**
     * Locks the slider, overrides YAHOO.util.DragDrop
     * @method lock
     */
    lock: function() {
        this.thumb.lock();
        this.locked = true;
    },

    /**
     * Unlocks the slider, overrides YAHOO.util.DragDrop
     * @method unlock
     */
    unlock: function() {
        this.thumb.unlock();
        this.locked = false;
    },

    /**
     * Handles mouseup event on the thumb
     * @method thumbMouseUp
     * @private
     */
    thumbMouseUp: function() {
        if (!this.isLocked() && !this.moveComplete) {
            this.endMove();
        }

    },

    onMouseUp: function() {
        if (this.backgroundEnabled && !this.isLocked() && !this.moveComplete) {
            this.endMove();
        }
    },

    /**
     * Returns a reference to this slider's thumb
     * @method getThumb
     * @return {SliderThumb} this slider's thumb
     */
    getThumb: function() {
        return this.thumb;
    },

    /**
     * Try to focus the element when clicked so we can add
     * accessibility features
     * @method focus
     * @private
     */
    focus: function() {
        this.valueChangeSource = this.SOURCE_UI_EVENT;

        // Focus the background element if possible
        var el = this.getEl();

        if (el.focus) {
            try {
                el.focus();
            } catch(e) {
                // Prevent permission denied unhandled exception in FF that can
                // happen when setting focus while another element is handling
                // the blur.  @TODO this is still writing to the error log 
                // (unhandled error) in FF1.5 with strict error checking on.
            }
        }

        this.verifyOffset();

        if (this.isLocked()) {
            return false;
        } else {
            this._slideStart();
            return true;
        }
    },

    /**
     * Event that fires when the value of the slider has changed
     * @method onChange
     * @param {int} firstOffset the number of pixels the thumb has moved
     * from its start position. Normal horizontal and vertical sliders will only
     * have the firstOffset.  Regions will have both, the first is the horizontal
     * offset, the second the vertical.
     * @param {int} secondOffset the y offset for region sliders
     * @deprecated use instance.subscribe("change") instead
     */
    onChange: function (firstOffset, secondOffset) { 
        /* override me */ 
    },

    /**
     * Event that fires when the at the beginning of the slider thumb move
     * @method onSlideStart
     * @deprecated use instance.subscribe("slideStart") instead
     */
    onSlideStart: function () { 
        /* override me */ 
    },

    /**
     * Event that fires at the end of a slider thumb move
     * @method onSliderEnd
     * @deprecated use instance.subscribe("slideEnd") instead
     */
    onSlideEnd: function () { 
        /* override me */ 
    },

    /**
     * Returns the slider's thumb offset from the start position
     * @method getValue
     * @return {int} the current value
     */
    getValue: function () { 
        return this.thumb.getValue();
    },

    /**
     * Returns the slider's thumb X offset from the start position
     * @method getXValue
     * @return {int} the current horizontal offset
     */
    getXValue: function () { 
        return this.thumb.getXValue();
    },

    /**
     * Returns the slider's thumb Y offset from the start position
     * @method getYValue
     * @return {int} the current vertical offset
     */
    getYValue: function () { 
        return this.thumb.getYValue();
    },

    /**
     * Internal handler for the slider thumb's onChange event
     * @method handleThumbChange
     * @private
     */
    handleThumbChange: function () { 
        /*
        var t = this.thumb;
        if (t._isRegion) {

            if (!this._silent) {
                t.onChange(t.getXValue(), t.getYValue());
                this.fireEvent("change", { x: t.getXValue(), y: t.getYValue() } );
            }
        } else {
            if (!this._silent) {
                t.onChange(t.getValue());
                this.fireEvent("change", t.getValue());
            }
        }
        */

    },

    /**
     * Provides a way to set the value of the slider in code.
     * @method setValue
     * @param {int} newOffset the number of pixels the thumb should be
     * positioned away from the initial start point 
     * @param {boolean} skipAnim set to true to disable the animation
     * for this move action (but not others).
     * @param {boolean} force ignore the locked setting and set value anyway
     * @param {boolean} silent when true, do not fire events
     * @return {boolean} true if the move was performed, false if it failed
     */
    setValue: function(newOffset, skipAnim, force, silent) {

        this._silent = silent;
        this.valueChangeSource = this.SOURCE_SET_VALUE;

        if (!this.thumb.available) {
            this.deferredSetValue = arguments;
            return false;
        }

        if (this.isLocked() && !force) {
            return false;
        }

        if ( isNaN(newOffset) ) {
            return false;
        }

        var t = this.thumb;
        t.lastOffset = [newOffset, newOffset];
        var newX, newY;
        this.verifyOffset(true);
        if (t._isRegion) {
            return false;
        } else if (t._isHoriz) {
            this._slideStart();
            // this.fireEvent("slideStart");
            newX = t.initPageX + newOffset + this.thumbCenterPoint.x;
            this.moveThumb(newX, t.initPageY, skipAnim);
        } else {
            this._slideStart();
            // this.fireEvent("slideStart");
            newY = t.initPageY + newOffset + this.thumbCenterPoint.y;
            this.moveThumb(t.initPageX, newY, skipAnim);
        }

        return true;
    },

    /**
     * Provides a way to set the value of the region slider in code.
     * @method setRegionValue
     * @param {int} newOffset the number of pixels the thumb should be
     * positioned away from the initial start point (x axis for region)
     * @param {int} newOffset2 the number of pixels the thumb should be
     * positioned away from the initial start point (y axis for region)
     * @param {boolean} skipAnim set to true to disable the animation
     * for this move action (but not others).
     * @param {boolean} force ignore the locked setting and set value anyway
     * @param {boolean} silent when true, do not fire events
     * @return {boolean} true if the move was performed, false if it failed
     */
    setRegionValue: function(newOffset, newOffset2, skipAnim, force, silent) {

        this._silent = silent;

        this.valueChangeSource = this.SOURCE_SET_VALUE;

        if (!this.thumb.available) {
            this.deferredSetRegionValue = arguments;
            return false;
        }

        if (this.isLocked() && !force) {
            return false;
        }

        if ( isNaN(newOffset) ) {
            return false;
        }

        var t = this.thumb;
        t.lastOffset = [newOffset, newOffset2];
        this.verifyOffset(true);
        if (t._isRegion) {
            this._slideStart();
            var newX = t.initPageX + newOffset + this.thumbCenterPoint.x;
            var newY = t.initPageY + newOffset2 + this.thumbCenterPoint.y;
            this.moveThumb(newX, newY, skipAnim);
            return true;
        }

        return false;

    },

    /**
     * Checks the background position element position.  If it has moved from the
     * baseline position, the constraints for the thumb are reset
     * @param checkPos {boolean} check the position instead of using cached value
     * @method verifyOffset
     * @return {boolean} True if the offset is the same as the baseline.
     */
    verifyOffset: function(checkPos) {

        var xy = YAHOO.util.Dom.getXY(this.getEl()),
            t  = this.thumb;

        if (xy) {


            if (xy[0] != this.baselinePos[0] || xy[1] != this.baselinePos[1]) {

                // Reset background
                this.setInitPosition();
                this.baselinePos = xy;

                // Reset thumb
                t.initPageX = this.initPageX + t.startOffset[0];
                t.initPageY = this.initPageY + t.startOffset[1];
                //t.deltaSetXY = [-this.initPageX,-this.initPageY];
                t.deltaSetXY = null;
                //this.resetConstraints();
                this.resetThumbConstraints();

                return false;
            }
        }

        return true;
    },

    /**
     * Move the associated slider moved to a timeout to try to get around the 
     * mousedown stealing moz does when I move the slider element between the 
     * cursor and the background during the mouseup event
     * @method moveThumb
     * @param {int} x the X coordinate of the click
     * @param {int} y the Y coordinate of the click
     * @param {boolean} skipAnim don't animate if the move happend onDrag
     * @param {boolean} midMove set to true if this is not terminating
     * the slider movement
     * @private
     */
    moveThumb: function(x, y, skipAnim, midMove) {

        var t = this.thumb;
        var self = this;

        if (!t.available) {
            return;
        }


        t.setDelta(this.thumbCenterPoint.x, this.thumbCenterPoint.y);

        var _p = t.getTargetCoord(x, y);
        var p = [Math.round(_p.x), Math.round(_p.y)];

        this._slideStart();

        if (this.animate && YAHOO.widget.Slider.ANIM_AVAIL && t._graduated && !skipAnim) {
            // this.thumb._animating = true;
            this.lock();

            // cache the current thumb pos
            this.curCoord = YAHOO.util.Dom.getXY(this.thumb.getEl());
            this.curCoord = [Math.round(this.curCoord[0]), Math.round(this.curCoord[1])];

            setTimeout( function() { self.moveOneTick(p); }, this.tickPause );

        } else if (this.animate && YAHOO.widget.Slider.ANIM_AVAIL && !skipAnim) {

            // this.thumb._animating = true;
            this.lock();

            var oAnim = new YAHOO.util.Motion( 
                    t.id, { points: { to: p } }, 
                    this.animationDuration, 
                    YAHOO.util.Easing.easeOut );

            oAnim.onComplete.subscribe( function() { 
                    
                    self.endMove(); 
                } );
            oAnim.animate();
        } else {
            t.setDragElPos(x, y);
            // this.fireEvents();
            if (!midMove) {
                this.endMove();
            }
        }
    },

    _slideStart: function() {
        if (!this._sliding) {
            if (!this._silent) {
                this.onSlideStart();
                this.fireEvent("slideStart");
            }
            this._sliding = true;
        }
    },

    _slideEnd: function() {

        if (this._sliding && this.moveComplete) {
            // Reset state before firing slideEnd
            var silent = this._silent;
            this._sliding = false;
            this._silent = false;
            this.moveComplete = false;
            if (!silent) {
                this.onSlideEnd();
                this.fireEvent("slideEnd");
            }
        }
    },

    /**
     * Move the slider one tick mark towards its final coordinate.  Used
     * for the animation when tick marks are defined
     * @method moveOneTick
     * @param {int[]} the destination coordinate
     * @private
     */
    moveOneTick: function(finalCoord) {

        var t = this.thumb, tmp;


        // redundant call to getXY since we set the position most of time prior 
        // to getting here.  Moved to this.curCoord
        //var curCoord = YAHOO.util.Dom.getXY(t.getEl());

        // alignElWithMouse caches position in lastPageX, lastPageY .. doesn't work
        //var curCoord = [this.lastPageX, this.lastPageY];

        // var thresh = Math.min(t.tickSize + (Math.floor(t.tickSize/2)), 10);
        // var thresh = 10;
        // var thresh = t.tickSize + (Math.floor(t.tickSize/2));

        var nextCoord = null,
            tmpX, tmpY;

        if (t._isRegion) {
            nextCoord = this._getNextX(this.curCoord, finalCoord);
            tmpX = (nextCoord !== null) ? nextCoord[0] : this.curCoord[0];
            nextCoord = this._getNextY(this.curCoord, finalCoord);
            tmpY = (nextCoord !== null) ? nextCoord[1] : this.curCoord[1];

            nextCoord = tmpX !== this.curCoord[0] || tmpY !== this.curCoord[1] ?
                [ tmpX, tmpY ] : null;
        } else if (t._isHoriz) {
            nextCoord = this._getNextX(this.curCoord, finalCoord);
        } else {
            nextCoord = this._getNextY(this.curCoord, finalCoord);
        }


        if (nextCoord) {

            // cache the position
            this.curCoord = nextCoord;

            // move to the next coord
            // YAHOO.util.Dom.setXY(t.getEl(), nextCoord);

            // var el = t.getEl();
            // YAHOO.util.Dom.setStyle(el, "left", (nextCoord[0] + this.thumb.deltaSetXY[0]) + "px");
            // YAHOO.util.Dom.setStyle(el, "top",  (nextCoord[1] + this.thumb.deltaSetXY[1]) + "px");

            this.thumb.alignElWithMouse(t.getEl(), nextCoord[0] + this.thumbCenterPoint.x, nextCoord[1] + this.thumbCenterPoint.y);
            
            // check if we are in the final position, if not make a recursive call
            if (!(nextCoord[0] == finalCoord[0] && nextCoord[1] == finalCoord[1])) {
                var self = this;
                setTimeout(function() { self.moveOneTick(finalCoord); }, 
                        this.tickPause);
            } else {
                this.endMove();
            }
        } else {
            this.endMove();
        }

        //this.tickPause = Math.round(this.tickPause/2);
    },

    /**
     * Returns the next X tick value based on the current coord and the target coord.
     * @method _getNextX
     * @private
     */
    _getNextX: function(curCoord, finalCoord) {
        var t = this.thumb;
        var thresh;
        var tmp = [];
        var nextCoord = null;
        if (curCoord[0] > finalCoord[0]) {
            thresh = t.tickSize - this.thumbCenterPoint.x;
            tmp = t.getTargetCoord( curCoord[0] - thresh, curCoord[1] );
            nextCoord = [tmp.x, tmp.y];
        } else if (curCoord[0] < finalCoord[0]) {
            thresh = t.tickSize + this.thumbCenterPoint.x;
            tmp = t.getTargetCoord( curCoord[0] + thresh, curCoord[1] );
            nextCoord = [tmp.x, tmp.y];
        } else {
            // equal, do nothing
        }

        return nextCoord;
    },

    /**
     * Returns the next Y tick value based on the current coord and the target coord.
     * @method _getNextY
     * @private
     */
    _getNextY: function(curCoord, finalCoord) {
        var t = this.thumb;
        var thresh;
        var tmp = [];
        var nextCoord = null;

        if (curCoord[1] > finalCoord[1]) {
            thresh = t.tickSize - this.thumbCenterPoint.y;
            tmp = t.getTargetCoord( curCoord[0], curCoord[1] - thresh );
            nextCoord = [tmp.x, tmp.y];
        } else if (curCoord[1] < finalCoord[1]) {
            thresh = t.tickSize + this.thumbCenterPoint.y;
            tmp = t.getTargetCoord( curCoord[0], curCoord[1] + thresh );
            nextCoord = [tmp.x, tmp.y];
        } else {
            // equal, do nothing
        }

        return nextCoord;
    },

    /**
     * Resets the constraints before moving the thumb.
     * @method b4MouseDown
     * @private
     */
    b4MouseDown: function(e) {
        if (!this.backgroundEnabled) {
            return false;
        }

        this.thumb.autoOffset();
        //this.thumb.resetConstraints();
        this.resetThumbConstraints();
    },

    /**
     * Handles the mousedown event for the slider background
     * @method onMouseDown
     * @private
     */
    onMouseDown: function(e) {
        // this.resetConstraints(true);
        // this.thumb.resetConstraints(true);

        if (!this.backgroundEnabled || this.isLocked()) {
            return false;
        }

        var x = YAHOO.util.Event.getPageX(e);
        var y = YAHOO.util.Event.getPageY(e);

        this.focus();
        this.moveThumb(x, y);
    },

    /**
     * Handles the onDrag event for the slider background
     * @method onDrag
     * @private
     */
    onDrag: function(e) {
        if (this.backgroundEnabled && !this.isLocked()) {
            var x = YAHOO.util.Event.getPageX(e);
            var y = YAHOO.util.Event.getPageY(e);
            this.moveThumb(x, y, true, true);
            this.fireEvents();
        }
    },

    /**
     * Fired when the slider movement ends
     * @method endMove
     * @private
     */
    endMove: function () {
        // this._animating = false;
        this.unlock();
        this.moveComplete = true;
        this.fireEvents();
    },

    /**
     * Resets the X and Y contraints for the thumb.  Used in lieu of the thumb
     * instance's inherited resetConstraints because some logic was not
     * applicable.
     * @method resetThumbConstraints
     * @protected
     */
    resetThumbConstraints: function () {
        var t = this.thumb;

        t.setXConstraint(t.leftConstraint, t.rightConstraint, t.xTickSize);
        t.setYConstraint(t.topConstraint, t.bottomConstraint, t.xTickSize);
    },

    /**
     * Fires the change event if the value has been changed.  Ignored if we are in
     * the middle of an animation as the event will fire when the animation is
     * complete
     * @method fireEvents
     * @param {boolean} thumbEvent set to true if this event is fired from an event
     *                  that occurred on the thumb.  If it is, the state of the
     *                  thumb dd object should be correct.  Otherwise, the event
     *                  originated on the background, so the thumb state needs to
     *                  be refreshed before proceeding.
     * @private
     */
    fireEvents: function (thumbEvent) {

        var t = this.thumb;

        if (!thumbEvent) {
            t.cachePosition();
        }

        if (! this.isLocked()) {
            if (t._isRegion) {
                var newX = t.getXValue();
                var newY = t.getYValue();

                if (newX != this.previousX || newY != this.previousY) {
                    if (!this._silent) {
                        this.onChange(newX, newY);
                        this.fireEvent("change", { x: newX, y: newY });
                    }
                }

                this.previousX = newX;
                this.previousY = newY;

            } else {
                var newVal = t.getValue();
                if (newVal != this.previousVal) {
                    if (!this._silent) {
                        this.onChange( newVal );
                        this.fireEvent("change", newVal);
                    }
                }
                this.previousVal = newVal;
            }

            this._slideEnd();

        }
    },

    /**
     * Slider toString
     * @method toString
     * @return {string} string representation of the instance
     */
    toString: function () { 
        return ("Slider (" + this.type +") " + this.id);
    }

});

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

/**
 * A drag and drop implementation to be used as the thumb of a slider.
 * @class SliderThumb
 * @extends YAHOO.util.DD
 * @constructor
 * @param {String} id the id of the slider html element
 * @param {String} sGroup the group of related DragDrop items
 * @param {int} iLeft the number of pixels the element can move left
 * @param {int} iRight the number of pixels the element can move right
 * @param {int} iUp the number of pixels the element can move up
 * @param {int} iDown the number of pixels the element can move down
 * @param {int} iTickSize optional parameter for specifying that the element 
 * should move a certain number pixels at a time.
 */
YAHOO.widget.SliderThumb = function(id, sGroup, iLeft, iRight, iUp, iDown, iTickSize) {

    if (id) {
        //this.init(id, sGroup);
        YAHOO.widget.SliderThumb.superclass.constructor.call(this, id, sGroup);

        /**
         * The id of the thumbs parent HTML element (the slider background 
         * element).
         * @property parentElId
         * @type string
         */
        this.parentElId = sGroup;
    }


    //this.removeInvalidHandleType("A");


    /**
     * Overrides the isTarget property in YAHOO.util.DragDrop
     * @property isTarget
     * @private
     */
    this.isTarget = false;

    /**
     * The tick size for this slider
     * @property tickSize
     * @type int
     * @private
     */
    this.tickSize = iTickSize;

    /**
     * Informs the drag and drop util that the offsets should remain when
     * resetting the constraints.  This preserves the slider value when
     * the constraints are reset
     * @property maintainOffset
     * @type boolean
     * @private
     */
    this.maintainOffset = true;

    this.initSlider(iLeft, iRight, iUp, iDown, iTickSize);

    /**
     * Turns off the autoscroll feature in drag and drop
     * @property scroll
     * @private
     */
    this.scroll = false;

}; 

YAHOO.extend(YAHOO.widget.SliderThumb, YAHOO.util.DD, {

    /**
     * The (X and Y) difference between the thumb location and its parent 
     * (the slider background) when the control is instantiated.
     * @property startOffset
     * @type [int, int]
     */
    startOffset: null,

    /**
     * Override the default setting of dragOnly to true.
     * @property dragOnly
     * @type boolean
     * @default true
     */
    dragOnly : true,

    /**
     * Flag used to figure out if this is a horizontal or vertical slider
     * @property _isHoriz
     * @type boolean
     * @private
     */
    _isHoriz: false,

    /**
     * Cache the last value so we can check for change
     * @property _prevVal
     * @type int
     * @private
     */
    _prevVal: 0,

    /**
     * The slider is _graduated if there is a tick interval defined
     * @property _graduated
     * @type boolean
     * @private
     */
    _graduated: false,


    /**
     * Returns the difference between the location of the thumb and its parent.
     * @method getOffsetFromParent
     * @param {[int, int]} parentPos Optionally accepts the position of the parent
     * @type [int, int]
     */
    getOffsetFromParent0: function(parentPos) {
        var myPos = YAHOO.util.Dom.getXY(this.getEl());
        var ppos  = parentPos || YAHOO.util.Dom.getXY(this.parentElId);

        return [ (myPos[0] - ppos[0]), (myPos[1] - ppos[1]) ];
    },

    getOffsetFromParent: function(parentPos) {

        var el = this.getEl(), newOffset;

        if (!this.deltaOffset) {

            var myPos = YAHOO.util.Dom.getXY(el);
            var ppos  = parentPos || YAHOO.util.Dom.getXY(this.parentElId);

            newOffset = [ (myPos[0] - ppos[0]), (myPos[1] - ppos[1]) ];

            var l = parseInt( YAHOO.util.Dom.getStyle(el, "left"), 10 );
            var t = parseInt( YAHOO.util.Dom.getStyle(el, "top" ), 10 );

            var deltaX = l - newOffset[0];
            var deltaY = t - newOffset[1];

            if (isNaN(deltaX) || isNaN(deltaY)) {
            } else {
                this.deltaOffset = [deltaX, deltaY];
            }

        } else {
            var newLeft = parseInt( YAHOO.util.Dom.getStyle(el, "left"), 10 );
            var newTop  = parseInt( YAHOO.util.Dom.getStyle(el, "top" ), 10 );

            newOffset  = [newLeft + this.deltaOffset[0], newTop + this.deltaOffset[1]];
        }

        return newOffset;

        //return [ (myPos[0] - ppos[0]), (myPos[1] - ppos[1]) ];
    },

    /**
     * Set up the slider, must be called in the constructor of all subclasses
     * @method initSlider
     * @param {int} iLeft the number of pixels the element can move left
     * @param {int} iRight the number of pixels the element can move right
     * @param {int} iUp the number of pixels the element can move up
     * @param {int} iDown the number of pixels the element can move down
     * @param {int} iTickSize the width of the tick interval.
     */
    initSlider: function (iLeft, iRight, iUp, iDown, iTickSize) {


        //document these.  new for 0.12.1
        this.initLeft = iLeft;
        this.initRight = iRight;
        this.initUp = iUp;
        this.initDown = iDown;

        this.setXConstraint(iLeft, iRight, iTickSize);
        this.setYConstraint(iUp, iDown, iTickSize);

        if (iTickSize && iTickSize > 1) {
            this._graduated = true;
        }

        this._isHoriz  = (iLeft || iRight); 
        this._isVert   = (iUp   || iDown);
        this._isRegion = (this._isHoriz && this._isVert); 

    },

    /**
     * Clear's the slider's ticks
     * @method clearTicks
     */
    clearTicks: function () {
        YAHOO.widget.SliderThumb.superclass.clearTicks.call(this);
        this.tickSize = 0;
        this._graduated = false;
    },


    /**
     * Gets the current offset from the element's start position in
     * pixels.
     * @method getValue
     * @return {int} the number of pixels (positive or negative) the
     * slider has moved from the start position.
     */
    getValue: function () {
        return (this._isHoriz) ? this.getXValue() : this.getYValue();
    },

    /**
     * Gets the current X offset from the element's start position in
     * pixels.
     * @method getXValue
     * @return {int} the number of pixels (positive or negative) the
     * slider has moved horizontally from the start position.
     */
    getXValue: function () {
        if (!this.available) { 
            return 0; 
        }
        var newOffset = this.getOffsetFromParent();
        if (YAHOO.lang.isNumber(newOffset[0])) {
            this.lastOffset = newOffset;
            return (newOffset[0] - this.startOffset[0]);
        } else {
            return (this.lastOffset[0] - this.startOffset[0]);
        }
    },

    /**
     * Gets the current Y offset from the element's start position in
     * pixels.
     * @method getYValue
     * @return {int} the number of pixels (positive or negative) the
     * slider has moved vertically from the start position.
     */
    getYValue: function () {
        if (!this.available) { 
            return 0; 
        }
        var newOffset = this.getOffsetFromParent();
        if (YAHOO.lang.isNumber(newOffset[1])) {
            this.lastOffset = newOffset;
            return (newOffset[1] - this.startOffset[1]);
        } else {
            return (this.lastOffset[1] - this.startOffset[1]);
        }
    },

    /**
     * Thumb toString
     * @method toString
     * @return {string} string representation of the instance
     */
    toString: function () { 
        return "SliderThumb " + this.id;
    },

    /**
     * The onchange event for the handle/thumb is delegated to the YAHOO.widget.Slider
     * instance it belongs to.
     * @method onChange
     * @private
     */
    onChange: function (x, y) { 
    }

});

/**
 * 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) {
        return self._handleMouseDown(e);
    };

    // we can safely ignore a mousedown on one of the sliders since
    // they share a background
    maxSlider.onMouseDown = function(e) { 
        if (self.minSlider.isLocked() && !self.minSlider._sliding) {
            return self._handleMouseDown(e);
        } else {
            YAHOO.util.Event.stopEvent(e); 
            return false;
        }
    };

    // 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);
            }
        });

        // Must emit Slider slideEnd event to propagate to updateValue
        mins.setValue(min,skipAnim,force,false);
        maxs.setValue(max,skipAnim,force,false);
    },

    /**
     * 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,
                maxt = this.maxSlider.thumb,
                dim  = this.isHoriz ? 'x' : 'y';

            var thumbInnerWidth = this.minSlider.thumbCenterPoint[dim] +
                                  this.maxSlider.thumbCenterPoint[dim];

            // 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,
            max = this.maxSlider,
            minLocked = min.isLocked(),
            maxLocked = max.isLocked(),
            Ev  = YAHOO.util.Event,
            d;

        if (minLocked || maxLocked) {
            this.activeSlider = minLocked ? max : min;
        } else {
            if (this.isHoriz) {
                d = Ev.getPageX(e)-min.thumb.initPageX-min.thumbCenterPoint.x;
            } else {
                d = Ev.getPageY(e)-min.thumb.initPageY-min.thumbCenterPoint.y;
            }
                    
            this.activeSlider = d*2 > max.getValue()+min.getValue() ? max : min;
        }
    },

    /**
     * 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);
};
YAHOO.register("slider", YAHOO.widget.Slider, {version: "2.6.0", build: "1321"});

if (typeof(Hotels) == "undefined") {
	Hotels = new Object();
}

if (typeof(Hotels.Widgets) == "undefined") {
	Hotels.Widgets = new Object();
}

if (typeof(Hotels.Widgets.DoubleSlider) == "undefined") {
	
	Hotels.Widgets.DoubleSlider = Class.create();
	YAHOO.extend(Hotels.Widgets.DoubleSlider, Object, {
		
		initialize: function(cfg) {
			this.cfg = {
				handleImageMin: "/images/r3d3sign07/results/slider.png",
				handleImageMax: "/images/r3d3sign07/results/slider.png",
				handleWidth: 13,
				handleHeight: 18,
				highlightImage: "/images/r3d3sign07/results/slider_highlight.png",
				highlightHeight: 12,
				trackHeight: 17
			};
			
			for (var key in cfg) {
				this.cfg[key] = cfg[key]
			}
			this.id = "DS" + Math.random();
			YAHOO.util.Event.onDOMReady(this.initOnLoad, this, true)
		},

		initOnLoad: function() {
			try {
			var _this = this;
			var cfg = this.cfg;
			var Dom = YAHOO.util.Dom;
			
			// Create divs
			this.track = this.createTrack();
			this.minHandle = this.createHandle("_minHandle",cfg.handleImageMin,cfg.handleWidth,cfg.handleHeight);
			this.maxHandle = this.createHandle("_maxHandle",cfg.handleImageMax,cfg.handleWidth,cfg.handleHeight);
			
			// Do some calcs
			cfg.pxRange = parseInt(YAHOO.util.Dom.getStyle(this.track,"width"));
			cfg.range = cfg.maximum - cfg.minimum;
			cfg.tickSize = Math.floor(cfg.pxRange / (((cfg.maximum - cfg.minimum) / cfg.step)));
			cfg.pxRange = cfg.tickSize * (((cfg.maximum - cfg.minimum) / cfg.step));

			// Handle input field updates.
			if (cfg.fromInput) {
				this.customFromInput = cfg.fromInput;
				cfg.fromInput = null;
			}
			
			if (cfg.toInput) {
				this.customToInput = cfg.toInput;
				cfg.toInput = null;
			}

			if (cfg.minFieldId) {
				this.minField = $(cfg.minFieldId);
			}
			
			if (cfg.maxFieldId) {
				this.maxField = $(cfg.maxFieldId);
			}
			
			if (this.minField) {
				YAHOO.util.Event.addListener(cfg.minFieldId, "change", this.updateSlider, this, true);
			}
			if (this.maxField) {
				YAHOO.util.Event.addListener(cfg.maxFieldId, "change", this.updateSlider, this, true);
			}
			
			// Create slider
			this.slider = YAHOO.widget.Slider.getHorizDualSlider(this.track, 
				this.minHandle, this.maxHandle, cfg.pxRange, cfg.tickSize);

			// Override slider logic to allow closer sliders
			this.slider.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,
							maxt = this.maxSlider.thumb,
							dim  = this.isHoriz ? 'x' : 'y';

						// Make a smaller inner width so we can squeeze the thumbs closer together.
						var leftIW = Math.floor(cfg.tickSize) - 1;
						var rightIW = Math.ceil(cfg.tickSize) + 1;

						var minConstraint = Math.max(max-leftIW-this.minRange,0);
						var maxConstraint = Math.min(-min-rightIW-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);
					}
			}
			
			// Subscribe to get updates.
			this.slider.subscribe('ready', this.updateSlider, this, true);
			this.slider.subscribe('change', this.updateValues, this, true);
			} catch (e) { if (console) console.log("Error creating slider:" + e.message) }
			
			
			// Create slider ticks and lines
			try {
				this.createTickedTrack();
			} catch (e) { if (console) console.log("Error creating track:" + e.message) }
			
			// Create highlight between sliders
			try {
			this.createHighlight();
			} catch (e) { if (console) console.log("Error creating highlights:" + e.message) }
		},
		
		updateValues: function() {
			try {
			var cfg = this.cfg;
			Hotels.ClickTrack.click(cfg.fcClick);
			if (this.minField) 
				this.minField.value = this.toInput(this.slider.minVal);
			if (this.maxField)
				this.maxField.value = this.toInput(this.slider.maxVal);
			} catch(e) {if (console) console.log(e.message)}
		},
		
		updateSlider: function() {
			try {
			var cfg = this.cfg;
			var minField = this.minField;
			var maxField = this.maxField;
			
			if (minField) {
				var minVal=parseFloat(minField.value);
				if (isNaN(minVal)) {
					minVal = cfg.mininum;
				}
				minVal = this.fromInput(minVal)
				this.slider.setMinValue(minVal,true);
			}
	
			if (maxField) {
				var maxVal=parseFloat(maxField.value);
				if (isNaN(maxVal)) {
					maxVal = cfg.maxinum;
				}
				maxVal = this.fromInput(maxVal)
				this.slider.setMaxValue(maxVal,true);
			}
			} catch(e) {if (console) console.log(e.message)}
		},
		
		getRangedValue: function(val) {
			var cfg = this.cfg;
			var ceil = cfg.step
			return Math.round(((val / cfg.pxRange) * cfg.range) / ceil) * ceil + cfg.minimum;
		},
		
		getPxValue: function(val) {
			var cfg = this.cfg;
			var ceil = cfg.step
			return Math.round(((val - cfg.minimum) / cfg.range) * cfg.pxRange)
		},
		
		createTrack: function() {
			var trackDiv = $(this.cfg.placeHolderId);
			YAHOO.util.Dom.addClass(trackDiv, "slider-track");
			trackDiv.id = this.id + "_track";
			return $(this.id + "_track");
		},
		
		createHandle: function(idSuffix, imgUrl, imgWidth, imgHeight) {
			var trackDiv = this.track;
			var handleId = this.id + idSuffix;
			
			var handleDiv = document.createElement("div");
			handleDiv.id = handleId;
			YAHOO.util.Dom.addClass(handleDiv, "slider-handle");
			handleDiv.style.width = imgWidth + "px";
			handleDiv.style.height = imgHeight + "px";
			
			if (!imgUrl) imgUrl = "SliderHandle.png";
			
			if (isIE && /\.png$/i.test(imgUrl)) {
	  			handleDiv.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imgUrl + "',sizingMethod='scale')";
			} else {
				handleDiv.style.backgroundImage = "url(" + imgUrl + ")";
			}
		
			handleDiv.style.cursor = "pointer";
			trackDiv.appendChild(handleDiv);
	
			return handleDiv;
		},
		
		createHighlight: function() {
			var cfg = this.cfg;

			if (cfg.highlightImage) {
				var trackDiv = this.track;
				var slider = this.slider;
				var Dom = YAHOO.util.Dom;

				var highlightDiv = document.createElement("div");
				highlightDiv.id = this.id + "_highlight";
				var img = cfg.highlightImage;
				highlightDiv.style.height = cfg.highlightHeight + "px";
				highlightDiv.style.zIndex = 2;
				if (isIE && /\.png$/i.test(img)) {
	  				highlightDiv.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + img + "',sizingMethod='scale')";
				} else {
					highlightDiv.style.backgroundImage = "url(" + img + ")";
				}
				YAHOO.util.Dom.addClass(highlightDiv, "slider-highlight");
				trackDiv.appendChild(highlightDiv);

				//YAHOO.util.Event.on(window, "load", function() {
				YAHOO.lang.augmentObject(slider, {
					_highlight: highlightDiv,
					updateHighlight: function() {
						if (0 < this.minVal || cfg.pxRange > this.maxVal) {
							Dom.setStyle(this._highlight,'visibility','visible');
							var delta = this.maxVal - this.minVal;
							if (this.activeSlider === this.minSlider) {
								Dom.setStyle(this._highlight,'left',(this.minVal + cfg.handleWidth) + 'px');
							}
							Dom.setStyle(this._highlight,'width', Math.max(delta - cfg.handleWidth,0) + 'px');
						} else {
							Dom.setStyle(this._highlight,'visibility','hidden');
						} 
					}
				})
				//});
				slider.subscribe('change',slider.updateHighlight,slider,true);
				slider.updateHighlight();
			}
		},
		
		createTickedTrack: function() {
			var cfg = this.cfg;
			var trackDiv = this.track;
			var shortTick = parseInt(cfg.trackHeight * 2/3);
			var heights = [cfg.trackHeight + "px", shortTick + "px"];
			var tops = ["0px", (cfg.trackHeight - shortTick) + "px"];
			var hofst = parseInt(cfg.handleWidth / 2);
			var c = 0;
			var left;
			for (var i = 0; i <= cfg.pxRange; i += cfg.tickSize) {
				var tickDiv = document.createElement("div");
				YAHOO.util.Dom.addClass(tickDiv,"slider-tick");
				tickDiv.style.height = heights[c % 2];
				if (isIE) {
					tickDiv.innerHTML = "<p style='line-height:0px'></p>";
				}		
				tickDiv.style.left = parseInt(i + hofst) + "px";
				if (!left) {
					left = (parseInt(i + hofst) + 1) + "px"
				}
				tickDiv.style.top = tops[c % 2];
				trackDiv.appendChild(tickDiv);
				c++;
			}
		
			var bot = document.createElement("div");
			YAHOO.util.Dom.addClass(bot, "slider-tick-bottom");
			bot.style.left = left;
			bot.style.width = cfg.pxRange + "px";
			bot.style.top = (cfg.trackHeight - 2) + "px";
			if (isIE)
				bot.innerHTML = "<p style='line-height:0px'></p>";
			trackDiv.appendChild(bot);
		},
		
		fromInput: function(val) {
			var nVal = val;
			if (this.customFromInput) {
				nVal = this.customFromInput(val); 
			}
			nVal =  this.getPxValue(nVal);
			return nVal;
		},
		
		toInput: function(val) {
			var nVal = this.getRangedValue(val);
			if (this.customToInput) {
				nVal = this.customToInput(nVal);
			}
			return nVal;
		}
	})
	
}
if (typeof(Hotels)=="undefined") {
	Hotels = new Object();
}

if (typeof(Hotels.Results=="undefined")) {
	
	Hotels.Results = {
		numProperties: 20,
		isInitialized : false,

		/**
		 * Initialize
		 */
		init: function() {
			this.numProperties = this.properties.length; //parseInt($("numProperties").value);
			this.pageNumber = ($("pageNumber"))?parseInt($("pageNumber").value):1;
			if (this.pageNumber == 1) {
				this.startProp = 1;
			} else {
				if (this.pageNumber>1 && this.numProperties<25) {
					this.startProp = ((this.pageNumber-1) * 25)+1;
					this.numProperties = this.startProp + this.numProperties - 1;
				} else {
					this.startProp = this.numProperties;
					this.numProperties = this.numProperties*this.pageNumber;
					this.startProp = (this.numProperties-this.startProp)+1;
				}
			}
			this.isInitialized = true;
		},
		
		/**
		 * Register property object so we can do things to them en-mass
		 */
		properties: new Array(),
		register: function (prop) {
			this.properties.push(prop);
			
		},
		getProp: function(idx) {
			return this.properties[idx];
		},
		showErrors: function(msg) {
			$("errorAlert").style.display = "block";
			$("errorDivMessageSpan").innerHTML = msg;
			this.errorsHidden = false;
			for (var i = 0; i < this.properties.length; i++) {
				this.properties[i].showError()
			}
			if (typeof(Hotels.RefineSearch) != "undefined") 
				Hotels.RefineSearch.reflow();
			this.reflow(this.startProp);
		},
	
		hideErrors: function() {
			if (!this.errorsHidden) {
				$("errorAlert").style.display = "none";
				for (var i = 0; i < this.properties.length; i++) {
					this.properties[i].hideError()
				}
				if (typeof(Hotels.RefineSearch) != "undefined") 
					Hotels.RefineSearch.reflow();
				this.errorsHidden = true;
			}
		},
		
		/**
		 * Paging and sorting
		 */
		sort: function (dropdown) {
			if(this.isInitialized == true) {
				Hotels.MyForm.reset();
				var name = dropdown.options[dropdown.selectedIndex].value
				var form = Hotels.MyForm.get();
				form["sortBy"].value = name;
				Hotels.ClickTrack.click("Sort-"+name);
				form.submit();
			}
		},
		previousPage: function() {
			if(this.isInitialized == true) {
				if (Hotels.MyForm.get("paging").value > 1) {
					Hotels.MyForm.get("paging").value--;
				}
				Hotels.MyForm.submitNoChanges("Page-Prev")
	  		}
		},
		nextPage: function() {
			if (this.isInitialized == true) {
				Hotels.MyForm.get("paging").value++;
				Hotels.MyForm.submitNoChanges("Page-Next");
			}
		},
		// Goto a specific page of search results
		toPage: function(pageNum) {
			if(this.isInitialized == true) {
				Hotels.MyForm.get("paging").value = pageNum;
				Hotels.MyForm.submitNoChanges("Page-"+pageNum);
			}
		},
		
		/**
		 * Different form submits
		 */
		resolveMultipleDestinations: function() {
			Hotels.MyForm.get("chooseAlternateDestination").value = 'true';
    		Hotels.MyForm.submitNoChanges("Destination-Disambig");
		},
		changeTravelDetails: function() {
			Hotels.MyForm.get().action = '/changeTravelDetails.do';
			Hotels.MyForm.submitNoChanges("ChangeTravelDetails");
		},
		/**
		 * IE bugfix reflow
		 */
		reflow: function(num) {
			/**
			 * TODO Loop through properties and call some kind of compare enable
			 */
			if (isIE) {
				num = parseInt(num) + 1;
				var neighbor = document.getElementById("listing"+num)
				if (neighbor) {
					if (neighbor.className == 'highlightOff') {
						neighbor.className = 'highlightOn';
						neighbor.className = 'highlightOff';
					} else {
						neighbor.className = 'highlightOff';
						neighbor.className = 'highlightSelectedOn';
					}
					this.reflow(num);	
				}
			}
		},
		
		/**
		 * Compare functionality
		 */		
		MAX_COMPARE: 25,
		checkCompare: function() {
			var compared = Hotels.CompareList.count();
			if ( compared < this.MAX_COMPARE ) {
				Hotels.Results.hideErrors();
				if (this.compareDisabled) {
					for (var i = 0; i < this.properties.length; i++) {
						this.properties[i].enableCompare();
					}
					this.compareDisabled = false;
				}
			} else {
				// Greater than or equal, disable checking more.
				if (!this.compareDisabled) {
					for (var i = 0; i < this.properties.length; i++) {
						this.properties[i].disableCompare();
					}
					this.compareDisabled = true;
				}
				// Should never happen now that we disable checkboxes.
				if (compared > this.MAX_COMPARE) {
					Hotels.Results.showErrors("Only 25 properties will be displayed for comparison");
					return false;
				}
			}
			return true;
		},
		
		compare: function() {
			var compared = Hotels.CompareList.count();
			if (compared == 1) {
				this.showErrors("You must select at least two properties to compare");
			} else {
				if (compared > 1) {
					var myForm = Hotels.MyForm.get();
					myForm.action = "/compareHotels.do";
					Hotels.MyForm.submitNoChanges();
				}			
			}			
		}
	}
	
	Hotels.CompareList = {
		count: function() {
			var list = Hotels.MyForm.get("propertyIdsToCompareString").value.split("-");
			if (!list) {
				return 0;
			}
			return list.length - 1;
		},
		contains: function(propId) {
			return Hotels.MyForm.get("propertyIdsToCompareString").value.indexOf(propId) > -1;
		},
		add: function(propId) {
			if (!this.contains(propId)) {
				Hotels.MyForm.get("propertyIdsToCompareString").value += propId + "-";
			} 
		},
		remove: function(propId) {
			var regExp = new RegExp(propId + "-","g");
			var list = Hotels.MyForm.get("propertyIdsToCompareString").value;
			Hotels.MyForm.get("propertyIdsToCompareString").value = list.replace(regExp, "");
		}
	}	
	
	/* Need this alias to work with tiles correctly */
	function toPage(pageNum) {
		Hotels.Results.toPage(pageNum);
	}
	
	YAHOO.util.Event.onDOMReady(Hotels.Results.init, Hotels.Results, true);
}

/* Need this alias to work with tiles correctly */
function checkRates() {
	var form  = Hotels.MyForm.get();
	if(form != null) {
	    if (form.CIMonth.value == "-1" || form.COMonth.value == "-1") 
	        showDiv('propertyForm1');
	    else
	        Hotels.MyForm.submitChanges();
	}
}

if (typeof(Hotels)=="undefined") {
	Hotels = new Object();
}

if (typeof(Hotels.PropertyListing=="undefined")) {

	Hotels.template = /\{(\w*)\}/g;
	function renderTemplate(tmplt,data){
		return tmplt.replace(Hotels.template,function(match,key,index){return data[key];});
	}

	Hotels.PropertyListing = Class.create();
	Hotels.PropertyListing.prototype.initialize = function(cfg) {
		this.cfg = cfg;
		this.delayedLinkRenders = [];
		this.initLinkTemplate();
		Hotels.Results.register(this);
	}
	Hotels.PropertyListing.prototype.initCompare = function() {
		if (Hotels.CompareList.contains(this.cfg.propID)) {
			$("compare-" + this.cfg.propID).checked = true;
		}
		this.toggleCompare();
	}
	Hotels.PropertyListing.prototype.onload = function() {
		this.initCompare();
		var YUE = YAHOO.util.Event; 
		YUE.on($("compare-"+this.cfg.propID), "click", this.toggleCompare, this, true);
	}
	Hotels.PropertyListing.prototype.initLinkTemplate = function() {
		this.template = this.cfg.protoLink;
		this.template = this.template.replace('class="STYLE_CLASS"', '{styleClass}')
		this.template = this.template.replace('style="STYLE"', '{style}')
		this.template = this.template.replace("&MORE_PARAMS=GO_HERE", '{extra}');
		this.template = this.template.replace("LINK_CONTENT", '{linkContent}');
	}

	Hotels.PropertyListing.prototype.getLinkConfig = function(content, params, styleClass, style) {
		var data = new Object();
		data.linkContent = content;
		if (styleClass == '') {
			data.styleClass = '';
		} else {
			data.styleClass = 'class="' + styleClass + '"';
		}
		if (style == '') {
			data.style = '';
		} else {
			data.style = 'style="' + style + '"';
		}
		if (params == '') {
			data.extra = '';
		} else {
			data.extra = '&' + params;
		}
		return data;
	}
	
	Hotels.PropertyListing.prototype.getLink =  function (content, params, styleClass, style) {
		if (content == null || content == '') {
			return;
		} else {
			var data = this.getLinkConfig(content, params, styleClass, style);
			var template = this.template;
			return renderTemplate(template, data);
		}
	}

	Hotels.PropertyListing.prototype.renderLink = function (content, params, styleClass, style) {
		document.write(this.getLink(content, params, styleClass, style));
	}
	
	/**
	 * Generic popup for Property
	 */
	Hotels.PropertyListing.prototype.popup = function(url, name) {
		var w = window.open = (url, name,
			"toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,copyhistory=yes,width=590,height=350,screenX=700,screenY=110,top=300,left=100");
		w.focus();
	}
	/**
	 * Room Details Popup (Inside Property Details Dropdown)
	 */
	Hotels.PropertyListing.prototype.roomDetails = function(evt) {
		var url = "/roomDetail.do?property=" + this.cfg.propID 
			+ "&rDesc=" + this.cfg.desc 
			+ "&roomTypeId=" + this.cfg.roomTypeId 
			+ "&rateCode=" + this.cfg.ratePlanId;
		this.popup(url, "room_details_popup");
		YAHOO.util.Event.preventDefault(evt);
	}
	/**
	 * Cancellation Policy Popup (Inside Property Details Dropdown)
	 */
	Hotels.PropertyListing.prototype.cancellationPolicy = function(cfg) {
		var url = "/cancellationPolicy.do?property=" + this.cfg.propID 
			+ "&roomTypeCode=" + this.cfg.roomTypeId 
			+ "&rateCode=" + this.cfg.ratePlanId;
		this.popup(url, "cancellation_policy_popup");
		YAHOO.util.Event.preventDefault(evt);
	}
	
	Hotels.PropertyListing.prototype.toggleCompare = function () {
		var YUD = YAHOO.util.Dom;
		var propDataBox = $("propertyData-" + this.cfg.propID);
		var compareLabel = YUD.getElementsBy(function(){return true;}, "label", propDataBox);
		if ($('compare-'+this.cfg.propID).checked) {
			YUD.addClass(propDataBox, "compare-checked")
			YUD.addClass(compareLabel, "checked")
			YUD.removeClass("compare-action-"+this.cfg.propID, "hide");
			Hotels.CompareList.add(this.cfg.propID);
		} else {
			YUD.removeClass(propDataBox, "compare-checked")
			YUD.removeClass(compareLabel, "checked")
			YUD.addClass("compare-action-"+this.cfg.propID, "hide");
			Hotels.CompareList.remove(this.cfg.propID);
		}
		Hotels.Results.checkCompare();
	}
	
	Hotels.PropertyListing.prototype.showError = function() {
		var checkbox = $('compare-'+this.cfg.propID);
		if (checkbox.checked) {
			YAHOO.util.Dom.addClass(checkbox.parentNode,"error")
		} else {
			YAHOO.util.Dom.removeClass(checkbox.parentNode,"error")
		}
	}
	
	Hotels.PropertyListing.prototype.hideError = function() {
		YAHOO.util.Dom.removeClass($('compare-'+this.cfg.propID).parentNode,"error")
	}
	
	Hotels.PropertyListing.prototype.enableCompare = function() {
		$("compare-" + this.cfg.propID).disabled = false;
	}

	Hotels.PropertyListing.prototype.disableCompare = function() {
		$("compare-" + this.cfg.propID).disabled = true;
	}
	
	Hotels.PropertyListing.prototype.isComparable = function() {
		return $("compare-" + this.cfg.propID).checked;
	}

}

/**TODO change these methods to use the ones in the property listing.**/
function room_details(propID, str, roomTypeId, ratePlanId) {
    myUrl = "/roomDetail.do?property=" + propID + "&rDesc=" + str + "&roomTypeId=" + roomTypeId + "&rateCode=" + ratePlanId;
    var x=window.open(myUrl, "room_details_popup", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,copyhistory=yes,width=590,height=350,screenX=700,screenY=110,top=300,left=100");
    x.focus();
}

function cancellationPolicy(propId, roomTypeId, ratePlanId) {
    url = "/cancellationPolicy.do?property=" + propId + "&roomTypeCode=" + roomTypeId + "&rateCode=" + ratePlanId;
    var w = window.open(url, "cancellation_policy_popup", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,copyhistory=yes,width=590,height=350,screenX=700,screenY=110,top=300,left=100");
    w.focus();
}



var mapExpandInterval;
var MAP_FRAME_SIZE = 440;
var MAP_EXPAND_INTERVAL = 50;
var MAP_EXPAND_INCREMENT = 50;

function setLinkImage(image,hoverImage) {

      var oldimg = document.getElementById("mapImage");
      oldimg.src = image;
      if (hoverImage) {
            oldimg.onmouseover = function() {
                  oldimg.src = hoverImage;
            }
            oldimg.onmouseout = function() {
                  oldimg.src = image;
            }
      } else {
            oldimg.onmouseover = null;
            oldimg.onmouseout = null;
      }
}



function showMap(smooth){
	var iframe = document.getElementById("mapFrame");
	var div = document.getElementById("mapDiv");

	if(iframe == null) {
		var iframe = document.createElement("iframe");
		iframe.setAttribute("id", "mapFrame");
		iframe.setAttribute("frameBorder", "0");
		iframe.setAttribute("vspace", "0");
		iframe.setAttribute("hspace", "0");
		iframe.setAttribute("marginWidth", "0");
		iframe.setAttribute("marginHeight", "0");
		iframe.setAttribute("width", "750");
		iframe.setAttribute("height", "0");
		iframe.setAttribute("scrolling", "no");
		iframe.setAttribute("src", "/resultsMap.do");

		div.appendChild(iframe);
	}

	iframe.style.display = "block";
	if (smooth) {
		mapExpandInterval = setInterval("expandIFrameSmoothly()", MAP_EXPAND_INTERVAL);
	} else {
		iframe.setAttribute("height", MAP_FRAME_SIZE);
	}
	YAHOO.util.Dom.addClass("mapLinkAnchor", "mapon");
	$('mapCloser').style.display = "block";
	setLinkImage(Hotels.Results.MAP_ON, Hotels.Results.MAP_HOVER);
}

function hideMap(smooth){
	var iframe = document.getElementById("mapFrame");
	if(iframe != null) {
		if (smooth) {
			mapExpandInterval = setInterval("collapseIFrameSmoothly()", MAP_EXPAND_INTERVAL);
		} else {
			onMapCollapse()
		}
	}
}

function showOrHideMap() {
	var text = getCookie("mapShown");

	if (text == "yes"){
		showMap(false);
	} else {
		hideMap(false);
	}

}

function toggleMap(evt){
	var frame = document.getElementById("mapFrame");
	var mapDiv = document.getElementById("mapLinkAnchor");

	if ((frame == null) || (frame.style.display == "none")) {
		showMap(true);
		mapDiv.onclick = function(e) {
			e = e || window.event; // IE test
			toggleMap(e);
			fc_click('Map-Expand', 'ad', ''); // Needs to match links.xml
			return false;
		}
		setCookie("mapShown", "yes", null, "/", getDomain());
	} else {
		hideMap(true);
		// Don't want to track fc_clicks for collapses.
		mapDiv.onclick = function(e) {
			e = e || window.event; // IE test
			toggleMap(e);
			return false;
		}
		setCookie("mapShown", "no", null, "/", getDomain());
	}
	
}


function expandIFrameSmoothly() {
	var done= false;
	var iframe = document.getElementById("mapFrame");
	var height = parseInt(iframe.getAttribute("height"));
	height += MAP_EXPAND_INCREMENT;
	if (height >= MAP_FRAME_SIZE) {
		height = MAP_FRAME_SIZE;
		done = true;
	}

	iframe.setAttribute("height", height);

	if (done){
		clearInterval(mapExpandInterval);
		if (Hotels.Results) {
			Hotels.Results.reflow((Hotels.MyForm.get("pageNumber").value - 1) * 25 + 1);
		}
		onMapExpanded();
	}
}

function collapseIFrameSmoothly() {
	var done= false;
	var iframe = document.getElementById("mapFrame");
	if (iframe) {
		var height = parseInt(iframe.getAttribute("height"));
		height -= MAP_EXPAND_INCREMENT;
		if (height <= 0) {
			height = 0;
			done = true;
		}

		iframe.setAttribute("height", height);
	} else {
		done = true;
	}

	if (done){
		clearInterval(mapExpandInterval);
		if (Hotels.Results) {
			Hotels.Results.reflow((Hotels.MyForm.get("pageNumber").value - 1) * 25 + 1);
		}
		onMapCollapsed();
	}
}

function onMapCollapsed() {
	var iframe = document.getElementById("mapFrame");
	iframe.style.display = "none";
	setLinkImage(Hotels.Results.MAP_OFF);
	YAHOO.util.Dom.removeClass("mapLinkAnchor", "mapon");
	$('mapCloser').style.display = "none";
}


function expandProperty(seqNum) {
	showDiv('expandedPropertyDiv' + seqNum);
	hideDiv('collapsedPropertyDiv' + seqNum);
}

function collapseProperty(seqNum) {
	showDiv('collapsedPropertyDiv' + seqNum);
	hideDiv('expandedPropertyDiv' + seqNum);
}

function showDiv(id) {
	var div = document.getElementById(id);
	if (div) {
		div.style.display = 'block';
	}
}

function hideDiv(id) {
	var div = document.getElementById(id);
	if (div) {
		div.style.display = 'none';
	}
}

Hotels.RefineSearchDropdown = Class.create();

YAHOO.extend(Hotels.RefineSearchDropdown, Hotels.RefineSearchWidget, {
	
	init: function() {
		var _this = this;
			window.setTimeout(function() {
				_this.initDropdown();
				_this.initToggle();
				_this.initEvents();
				_this.update();
				_this.initInput();
			}, 0);
	},
	
	initDropdownEffect: function() {
		var height = this.cfg.divDropdown.offsetHeight - 20;
		var YUD = YAHOO.util.Dom;
		var opacity = YUD.getStyle(this.cfg.divDropdown, "opacity");
		YUD.setStyle(this.cfg.divDropdown, "height", 0);
		return function( overlay, dur ) {
			var effect = new YAHOO.widget.ContainerEffect(overlay,
				{ attributes: {
					height: { to: height },
					opacity: { to: opacity } },
					duration: dur },
				{ attributes: {
					height: { to: 0 },
					opacity: { to: 0 } },
					duration: dur },
				overlay.element)
			
			effect.handleStartAnimateIn = function (type,args,obj) {
				YUD.addClass(obj.overlay.element, "hide-select");
				YUD.setStyle(obj.overlay.element, "visibility", "visible");
			};
	
			effect.handleCompleteAnimateIn = function (type,args,obj) {
				YUD.removeClass(obj.overlay.element, "hide-select");
				obj.overlay.cfg.refireEvent("iframe");
				obj.animateInCompleteEvent.fire();
			};
	
			effect.handleStartAnimateOut = function (type, args, obj) {
				YUD.addClass(obj.overlay.element, "hide-select");
			};
	
			effect.handleCompleteAnimateOut =  function (type, args, obj) {
				YUD.removeClass(obj.overlay.element, "hide-select");
				YUD.setStyle(obj.overlay.element, "visibility", "hidden");
				obj.overlay.cfg.refireEvent("iframe");
				obj.animateOutCompleteEvent.fire();
			};
			
			effect.init();
			return effect;
		};
	},
	
	initToggle: function() {
		if (this.cfg.openLink) {
			YAHOO.util.Event.on(this.cfg.openLink.id, "click", this.show, this, true);
			if (!isIE) {
				this.cfg.openLink.onmousedown = function(e) { if (e.preventDefault){e.preventDefault();} }; // prevent image drag
			}
		}
		if (this.cfg.closeLink) {
			YAHOO.util.Event.on(this.cfg.closeLink.id, "click", this.hide, this, true);
		}
	},
	
	initDropdown: function() {
		this.cfg.divDropdown.style.display = "block";
		this.DROPDOWN = this.initDropdownEffect();
		this.dropdown = new YAHOO.widget.Overlay(this.cfg.divDropdown.id, {
			effect: {effect: this.DROPDOWN, duration: 0.25}});
		if (this.cfg.zIndex) {
			this.dropdown.cfg.setProperty("zIndex", this.cfg.zIndex);
		}
		this.dropdown.hide();
	},
	
	initInput: function() {
		if (!this.cfg.noToggleOnFocus) {
			YAHOO.util.Event.on(this.cfg.divInput, "click", this.toggle, this, true);
		}
	},
	
	toggle: function() {
		if (this.dropdown.cfg.getProperty("visible")) {
			this.hide();
		} else {
			this.show();
		}
	},
	
	show: function() {
		if (this.dropdown) {
			this.dropdown.cfg.setProperty("context", [this.cfg.divInput.id,"tl","bl"]);
			this.dropdown.show();
		}
		Hotels.ClickTrack.click(this.fcClick);
	},
	
	hide: function() {
		if (this.dropdown)
			this.dropdown.hide();
	}

});
Hotels.RefineSearchSlider = Class.create();
	
YAHOO.lang.extend(Hotels.RefineSearchSlider, Hotels.RefineSearchWidget, {
	
	isTarget: null,
	
	initialize: function(cfg) {
		this.min=1; this.max=5; this.step=0.5;
		Hotels.RefineSearchSlider.superclass.initialize.call(this,cfg);
		if (typeof(this.cfg.ttHelpAnchorId) != "undefined" && this.cfg.ttHelpAnchorId != "") {
			Hotels.RefineSearch.Tooltip.addTooltip(this.cfg.ttHelpAnchorId, this.cfg.ttHelpText);
		}
	},
	
	init: function() {
		this.inputMin = $(this.cfg.inputMinId);
		this.inputMax = $(this.cfg.inputMaxId);
		this.initialMin = this.inputMin.value;
		this.initialMax = this.inputMax.value 	
		var _this = this;		
		this.slider = new Hotels.Widgets.DoubleSlider({
			baseUrl: Hotels.RefineSearch.baseUrl,
			fcClick: _this.fcClick,
			placeHolderId: _this.cfg.divSliderId,
			minimum: _this.min, maximum:_this.max, step:_this.step,
			initialMin: _this.initialMin, initialMax: _this.initialMax,
			fromInput: _this.fromInput,
			toInput: _this.toInput,
			minFieldId: _this.cfg.inputMinId,
			maxFieldId: _this.cfg.inputMaxId
		});
	},
	
	reset: function() {
		$(this.cfg.inputMinId).value = this.initialMin;
		$(this.cfg.inputMaxId).value = this.initialMax;
	},

	toInput: function(value) {
		return value.toFixed(1)
	}
	
});

	
/***
 * Price Range Slider
 */
Hotels.RefinePriceRange = Class.create();
YAHOO.lang.extend(Hotels.RefinePriceRange, Hotels.RefineSearchSlider, {
	initialize: function(cfg) {
		this.type = "Hotels.RefinePriceRange";
		this.fcClick = "PriceRange-Slide";

		if (typeof(cfg) == "undefined") {
			cfg = new Object();
		}
		cfg.divSliderId =  'priceSlider';
		cfg.inputMinId =  'minPrice';
		cfg.inputMaxId =  'maxPrice';		
		Hotels.RefinePriceRange.superclass.initialize.call(this, cfg);
		this.min = 0; 
		this.max = cfg.max; 
		this.step = cfg.step;
	},
	fromInput: function(value) {
		if (value > this.cfg.maximum) {
			return this.cfg.maximum;
		}
		if (typeof(value) == "string") {
			value = value.replace("$",""); 
		}
		return value
	},
	toInput: function(value) {
		if (value >= this.cfg.maximum) {
			return this.cfg.maximum + "+";
		}
		return value;
	},
	init: function() {
		Hotels.RefinePriceRange.superclass.init.call(this);
	    
		var maxVal = this.initialMax;
		var minVal = this.initialMin; 		
		this.inputMax.value = maxVal;
		this.inputMin.value = minVal;
		
		// Remove the plus sign if you focus.
		YAHOO.util.Event.on(this.inputMax, "focus",
			function() { this.inputMax.value = this.inputMax.value.replace("+","") },
		this, true);
	},
	beforeSubmit: function() {
		this.inputMax.value = this.fromInput(this.inputMax.value);
		this.inputMin.value = this.fromInput(this.inputMin.value);
	}
});

/**
 * Star Rating slider
 */
Hotels.RefineStarRating = Class.create();
YAHOO.lang.extend(Hotels.RefineStarRating, Hotels.RefineSearchSlider, {
	initialize: function(cfg){
		this.type = "Hotels.RefineStarRating";
		this.fcClick = "StarRating-Slide"
		
		if (typeof(cfg) == "undefined") cfg = new Object();
		cfg.ttHelpAnchorId =  "icon_star_rating";
		cfg.ttHelpWidth =  "300px";
		cfg.ttHelpZIndex =  200;
		cfg.divSliderId =  "starRatingSlider";
		cfg.inputMinId =  "minStarRating";
		cfg.inputMaxId =  "maxStarRating";
		
		Hotels.RefineStarRating.superclass.initialize.call(this,cfg);
	}
});

Hotels.RefineGuestRating = Class.create();
YAHOO.lang.extend(Hotels.RefineGuestRating, Hotels.RefineSearchSlider, {
	initialize: function(cfg) {
		this.type = "Hotels.RefineGuestRating";
		this.fcClick = "GuestReview-Slide";

		if (typeof(cfg) == "undefined") cfg = new Object();
		cfg.ttHelpAnchorId =  "icon_guest_rating"; 
		cfg.ttHelpWidth =  "300px";
		cfg.ttHelpZIndex =  200;
		cfg.divSliderId =  "guestRatingSlider";
		cfg.inputMinId =  "guestRatingMin";
		cfg.inputMaxId =  "guestRatingMax";

		Hotels.RefineGuestRating.superclass.initialize.call(this,cfg);
	}
});	


/***
 * Refine Amenities
 */
Hotels.RefineAmenities = Class.create();

YAHOO.extend(Hotels.RefineAmenities, Hotels.RefineSearchDropdown, {

	initialize : function(cfg) {
		this.type = "Hotels.RefineAmenities";
		this.fcClick = "Amenities-Open";
		this.MAX_TEXT_LENGTH = 32;
		
		if (typeof(cfg) == "undefined") cfg = new Object();
		cfg.divInput =  $("refineAmenityInput");
		cfg.divDropdown =  $("refineAmenityDropdown");
		cfg.closeLink =  $("refineAmentiyClose");
		cfg.openLink =  $("refineAmentiyLink");
		cfg.zIndex =  150;
		
		this.amnts = YAHOO.util.Dom.getElementsBy(this.returnTrue,"input",cfg.divDropdown);
		Hotels.RefineAmenities.superclass.initialize.call(this, cfg);
	},
	
	initEvents: function() {
		var iv = [];
		var YUE = YAHOO.util.Event;
		var a = this.amnts;
		for (var i = 0; i < a.length; i++) {
			iv[i] = a[i].checked;
			YUE.on(a[i], "click", this.update, this, true );
		}
		this.initValues = iv;
	},
	
	getSelectedAmenities: function() {
		var s = [];
		var a = this.amnts;
		for (var i = 0; i < a.length; i++) {
			if (a[i].checked) {
				s.push($(a[i].value).innerHTML);
			}
		}
		return s.join(", ");
	},
	
	update: function() {
		var s = this.getSelectedAmenities();
		if (s.length > this.MAX_TEXT_LENGTH) {
			s = s.substring(0, this.MAX_TEXT_LENGTH-2);
			s += "...";
		}
		if (s) {
			this.cfg.divInput.innerHTML = s;
		} else {
			this.cfg.divInput.innerHTML = "";
		}
	},
	
	reset: function() {
		var a = this.amnts;
		for (var i = 0; i < a.length; a++) {
			a[i].checked = this.initValues[i];
		}
	}

});
Hotels.RefineLandmark = Class.create();

YAHOO.extend(Hotels.RefineLandmark, Hotels.RefineSearchDropdown, {
	
	initialize: function(cfg) {
		this.type = "Hotels.RefineLandmark";
		this.fcClick = "Landmarks-Open";
		this.trimRegExp = /^\s+|\s+$/g;
		if (typeof(cfg) == "undefined") cfg = new Object();
		cfg.openLink =  $("landmarkLink");
		cfg.divInput =  $("destinationAcInput");
		cfg.divDropdown =  $("refineLandmark");
		cfg.drawerId = "landmarkDrawer";
		cfg.drawer =  $(cfg.drawerId);
		cfg.drawerAnchorId = "locationAnchor";
		cfg.drawerAnchor =  $(cfg.drawerAnchorId);
		cfg.noToggleOnFocus =  true;
		cfg.zIndex =  400;
		
		this.origInputValue = cfg.divInput.value;
		Hotels.RefineLandmark.superclass.initialize.call(this,cfg);
	},
	
	init: function() {
		var _this = this;
			window.setTimeout(function() {
				_this.initDropdown();
				_this.initDrawer();
				_this.initToggle();
				_this.initEvents();
				_this.update();
				_this.initInput();
				_this.inited = true;
			}, 0);
	},
	
	reflow: function() {
		if (this.drawerO)
			this.drawerO.cfg.setProperty("context", [this.cfg.drawerAnchorId,"tl","bl"]);
		if (this.dropdown)
			this.dropdown.cfg.setProperty("context", [this.cfg.drawerAnchorId,"tl","bl"]);
	},
		
	initDrawer: function() {
		// Init drawer
		this.drawerO = new YAHOO.widget.Overlay(this.cfg.drawerId, {iframe:false});
		this.drawerO.cfg.setProperty("context", [this.cfg.drawerAnchorId,"tl","bl"]);
		if (this.cfg.zIndex) {
			this.drawerO.cfg.setProperty("zIndex", this.cfg.zIndex + 100);
		}
		this.drawerO.show();
		// Cheating here:
		this.dropdown.cfg.setProperty("context", [this.cfg.drawerAnchorId,"tl","bl"]);
		
		Hotels.RefineSearch.onAutoCompleteDropdown.subscribe(this.hide, this, true);
		
	},
		
	initDropdownEffect: function() {
		var _this = this;
		var height = this.cfg.divDropdown.offsetHeight;
		var YUD = YAHOO.util.Dom;
		var opacity = YUD.getStyle(this.cfg.divDropdown, "opacity");
		var lis = YUD.getElementsBy(function() { return true; }, "li", this.cfg.divDropdown);
		var landmarkLen = lis.length;
		if (landmarkLen < 7){
			height = (landmarkLen * 23) + 2; //Hack to manage height for smaller number of landmarks
		}
		YUD.setStyle(this.cfg.divDropdown, "height", 0);
		return function( overlay, dur ) {
	        var effect = new YAHOO.widget.ContainerEffect(overlay,
	             { attributes: {
	                height: { to: height },
	                opacity: { to: opacity } },
	                duration: dur },
	
	            { attributes: {
	                height: { to: 0 },
	                opacity: { to: 0 } },
	                duration: dur },
	
	            overlay.element)
	
	        effect.handleStartAnimateIn = function (type,args,obj) {
				YUD.replaceClass(_this.cfg.drawer, "closed", "open");
	            YUD.addClass(obj.overlay.element, "hide-select");
	            YUD.setStyle(obj.overlay.element, "visibility", "visible");
	        };
	
	        effect.handleCompleteAnimateIn = function (type,args,obj) {
				var y = YUD.getXY(obj.overlay.element)[1];
				var oHeight = obj.overlay.element.offsetHeight;
				YUD.setY(_this.cfg.drawer, y + oHeight);
	            YUD.removeClass(obj.overlay.element, "hide-select");
	            obj.overlay.cfg.refireEvent("iframe");
	            obj.animateInCompleteEvent.fire();
	        };
	
			effect.handleTweenAnimateIn = function(type , args , obj) {
				var y = YUD.getXY(obj.overlay.element)[1];
				var oHeight = obj.overlay.element.offsetHeight;
				YUD.setY(_this.cfg.drawer, y + oHeight);
			};
				
			effect.handleTweenAnimateOut = effect.handleTweenAnimateIn
	
	        effect.handleStartAnimateOut = function (type, args, obj) {
				YUD.replaceClass(_this.cfg.drawer, "open", "closed");
	            YUD.addClass(obj.overlay.element, "hide-select");
	        };
	
	        effect.handleCompleteAnimateOut =  function (type, args, obj) {
				var anchor = YUD.getXY(_this.cfg.drawerAnchor);
				YUD.setY(_this.cfg.drawer, anchor[1] + _this.cfg.drawerAnchor.offsetHeight)
	            YUD.removeClass(obj.overlay.element, "hide-select");
	            YUD.setStyle(obj.overlay.element, "visibility", "hidden");
	            obj.overlay.cfg.refireEvent("iframe");
	            obj.animateOutCompleteEvent.fire();
	        };
			effect.init();
	        return effect;
	    };
	},
	
	initInput: function() {
		var form = Hotels.MyForm.get();
		this.initAcDestId = form.acDestinationId.value;
		this.initAcDestType = form.acDestinationType.value;
		this.initSortBy = form.sortBy.value;
	},
	
	reset: function() {
		var form = Hotels.MyForm.get();
		form.acDestinationId.value = this.initAcDestId;
		form.acDestinationType.value = this.initAcDestType;
		form.sortBy.value = this.initSortBy;
	},
	
	initEvents: function() {
		var YUE = YAHOO.util.Event;
		var lis = YAHOO.util.Dom.getElementsBy(function() { return true; }, "li", this.cfg.divDropdown);
		for (var i in lis) {
			YUE.on(lis[i], "click", this.onClick, this, true );
			if (isIE) {
				YUE.on(lis[i], "mouseover", function() {this.style.backgroundColor = "yellow"} );
				YUE.on(lis[i], "mouseout",  function() {this.style.backgroundColor = ""} );
			}
		}
	},
	
	onClick: function (evt) {
		this.hide();
		var li = YAHOO.util.Event.getTarget(evt);
		var form = Hotels.MyForm.get();
		form.acDestinationId.value = li.id;
		form.acDestinationType.value = "2";
		if (form.sortBy) 
			form.sortBy.value = 'PROXIMITY';
		this.update();
	},
	
	getSelectedLandmark: function() {
		var form = Hotels.MyForm.get();
		if (form.acDestinationType.value == "2") {
			return form.acDestinationId.value;
		} else {
			return null;
		}
	},
	
	isAddressSearch: function() {
		return $('searchType').value == 'address';
	},
	
	update: function() {
		var cfg = this.cfg;
		var selected = this.getSelectedLandmark();
		if (selected && selected != "") {
			var value = $(selected).innerText;
			if (!value){
				var value = $(selected).textContent;	
			}	
			value = value.replace(this.trimRegExp,"");
			if (cfg.divInput.labeler) {
				cfg.divInput.labeler.setValue(value);
			} else {
				cfg.divInput.value = value;
			}
			Hotels.RefineSearch.onChangeToDestSearch.fire();
		} else {
			if (this.isAddressSearch()) {		
				cfg.divInput.value = "";
			} else {
				cfg.divInput.value = this.origInputValue;
			}
		}
	}

});
/***
 * Refine PropertyType
 */
Hotels.RefinePropertyType = Class.create();

YAHOO.extend(Hotels.RefinePropertyType, Hotels.RefineSearchDropdown, {
	
	initialize: function (cfg) {
		this.type = "Hotels.RefinePropertyTypeWidget";
		this.fcClick = "PropType-Open";
		this.MAX_TEXT_LENGTH = 22;
		if (typeof(cfg) == "undefined") cfg = new Object();
		cfg.divInput =  $("propTypeInput");
		cfg.divDropdown =  $("propTypeDropdown");
		cfg.closeLink =  $("propTypeClose");
		cfg.openLink =  $("refinePropTypeLink");
		cfg.zIndex =  150;
	
		this.types = YAHOO.util.Dom.getElementsBy(function () {return true;}, "input", cfg.divDropdown);
		Hotels.RefinePropertyType.superclass.initialize.call(this,cfg);
	},
		
	getCheckedTypes: function() {
		var checked = [];
		var types = this.types
		for (var i = 0; i < types.length; i++) {
			if (types[i].name != "allPropertyTypesSelected") {
				if (types[i].checked) {
					var type = $(types[i].id + "_label").innerHTML;
					type = type.replace(/&amp;/, "&");
					checked.push(type); 
				}
			}
		}
		return checked;
	},
		
	initEvents: function() {
		this.initValues = []
		var types = this.types;
		var YUE = YAHOO.util.Event;
		for (var i = 0; i < types.length; i++) {
			this.initValues[i] = types[i].checked; 
			var func;
			if (types[i].name == "allPropertyTypesSelected") {
				func = function () {
					if(Hotels.MyForm.get("allPropertyTypesSelected").checked) {
						this.checkAllTypes();
					}
					this.update();
				}
			} else {
				func = function () {
					var anyChecked = this.getCheckedTypes();
					Hotels.MyForm.get("allPropertyTypesSelected").checked = !anyChecked;
					this.update();
				}
			}
			
			YUE.on(types[i], "click", func, this, true );
		}
	},
	
	checkAllTypes: function() {
		var types = this.types;
		for (var i = 0; i < types.length; i++) {
			types[i].checked = (types[i].name == "allPropertyTypesSelected");
		}
	},
	
	update: function() {
		var anyChecked = this.getCheckedTypes();
		if (anyChecked.length > 0) {
			var selected = anyChecked.join(", ");
			if (selected.length > this.MAX_TEXT_LENGTH) {
				selected = selected.substring(0, this.MAX_TEXT_LENGTH - 2);
				selected += "...";
			}
			this.cfg.divInput.innerHTML = selected; 
		} else {
			Hotels.MyForm.get()["allPropertyTypesSelected"].checked = true;
			this.cfg.divInput.innerHTML = Hotels.RefinePropertyType.ALL_TYPES;
		}
		Hotels.RefineSearch.onChangePropType.fire();
	},
	
	reset: function() {
		var types = this.types;
		for (var i = 0; i < types.length; i++) {
			types[i].checked = this.initValues[i]; 
		}
	}

});

Hotels.RefineRoomsGuests = Class.create();

YAHOO.extend(Hotels.RefineRoomsGuests, Hotels.RefineSearchWidget, {
	
	initialize: function(cfg) {
		this.type = "Hotels.RefineRoomsGuests";
		this.numRooms = 1;
		this.showing = false;
		this.rowStyles = ["room-grid-even", "room-grid-odd"];
		
		if (typeof(cfg) == "undefined") cfg = new Object();
		cfg.divTotalAdultsId = "adultsTotal";
		cfg.divTotalChildrenId = "childrenTotal";
		cfg.inputTotalAdultsId = "refineAdults";
		cfg.inputTotalChildrenId = "refineChildren";
		cfg.inputNumRoomsId = "refineRooms";
		cfg.dropDownAnchorId = "guestAnchor";
		cfg.divGuestId = "CollectDetails";
		cfg.roomsCloseId = "roomsClose"; 
		
		Hotels.RefineRoomsGuests.superclass.initialize.call(this,cfg);
		Hotels.RefineSearch.onChangePropType.subscribe(function(type, args) {
			this.update();
		}, this, true);
	},
	
	init: function() {
		var cfg = this.cfg;
		cfg.inputNumRooms = $(cfg.inputNumRoomsId);
		cfg.divGuest = $(cfg.divGuestId);
		cfg.dropDownAnchor = $(cfg.dropDownAnchorId);
		cfg.inputTotalAdults = $(cfg.inputTotalAdultsId);
		cfg.inputTotalChildren = $(cfg.inputTotalChildrenId);
		cfg.divTotalAdults = $(cfg.divTotalAdultsId);
		cfg.divTotalChildren = $(cfg.divTotalChildrenId);
		cfg.roomsClose = new YAHOO.widget.Overlay(this.cfg.roomsCloseId);
				
		this.form = cfg.inputNumRooms.form;
		YAHOO.util.Event.on(cfg.inputNumRooms, "change", function() {
			this.update();
			Hotels.ClickTrack.click("Rooms-Change");
		}, this, true);
		
		this.update();
		this.initOverlay();
		this.initInputs();
	},
	
	positionCloseButton: function() {
		this.cfg.roomsClose.cfg.setProperty("context", [this.cfg.dropDownAnchorId,"br","tr"]);
		$(this.cfg.roomsCloseId).style.top = (parseInt($(this.cfg.roomsCloseId).style.top) + 1) + "px"
	},
	
	reflowAnchor: true,
	
	initOverlay: function() {
		var _this = this;
		this.dropdown = new YAHOO.widget.Overlay(this.cfg.divGuestId);		
		this.cfg.divGuest.style.display = "block";
		this.dropdown.hide();
		if (this.cfg.dropDownAnchor) {
			this.anchorPoint = [_this.cfg.dropDownAnchorId,"tr","br"];
		} else {
			this.anchorPoint = [_this.cfg.inputNumRoomsId,"tl","bl"];
		}
		this.positionAnchor = function() {
			if (_this.reflowAnchor) {
				_this.dropdown.cfg.setProperty("context", _this.anchorPoint);
				_this.reflowAnchor = false;
			}
		}
			
		this.dropdown.cfg.setProperty("zIndex", 100);
		var yue = YAHOO.util.Event;
		yue.on(this.cfg.inputTotalAdults, "click", this.show, this, true);
		yue.on(this.cfg.inputTotalChildren, "click", this.show, this, true);
		yue.on(this.cfg.inputNumRooms, "change", this.show, this, true);
	},
		
	initInputs: function() {
		var form = this.form;
		for (i = 0; i < this.cfg.maxRooms; i++) {
			YAHOO.util.Event.on(form["adults[" + i + "]"], "change", function(){
					this.calcTotals();
					Hotels.ClickTrack.click("Guest-Change");
				}, this, true);
			YAHOO.util.Event.on(form["child[" + i + "]"], "change", function() {
					this.update(); 
					this.calcTotals();
					Hotels.ClickTrack.click("Guest-Change");
				}, this, true);
		}
	},
	
	validate: function() {
		var errors = [];
		var form = this.form;
		for (var i = 0; i < this.numRooms; i++) {
			var childs = parseInt(form["child[" + i + "]"].value);
			if (childs > 0) {
				for (var j = 0; j < childs; j++) {
					var cai = form["OIdx["+i+"].CAIdx["+j+"]"].value
					if (parseInt(cai) < 0) {
						errors.push("childAgeRequired");
					}
				}
			}
		}
		return errors;
	},
	
	setSelectValue: function(field, value) {
		var o = field.options;
		for (var i = 0; i < o.length; i++) {
			if (o[i].value == value) {
				field.selectedIndex = i;
				break;
			}
		}
	},
	
	setRooms: function(rooms) {
		this.setSelectValue(this.cfg.inputNumRooms, rooms)
		this.update();
	},
	
	setRoomAdults: function(room, adults) {
		this.setSelectValue(this.form["adults["+room+"]"], adults)
		this.updateAdultChildDropdowns(room);
	},
	
	setRoomChildren: function(room, children) {
		var form = this.form;
		this.setSelectValue(form["child["+room+"]"], children.length)
		this.updateAdultChildDropdowns(room);
		for (var i = 0; i < children.length; i++) {
			this.setSelectValue(form["OIdx["+room+"].CAIdx["+i+"]"], children[i])
		}
	},
	
	updateAdultChildDropdowns: function (r) {
		var adults = this.form["adults[" + r + "]"];
		if (adults && adults.options.length != this.maxGuest) {
			var selectedAdult = adults.value;
			if (selectedAdult > this.roomMaxGuests || selectedAdult < this.roomMinAdults) {
				selectedAdult = this.roomMinAdults * 2;
			}
			adults.options.length = this.roomMaxGuests - this.roomMinAdults;
			var newSelectedIndex = 1;
			for (var i = this.roomMinAdults; i <= this.roomMaxGuests; ++i) {
				adults.options[i-this.roomMinAdults] = new Option(i,i);
				if (i == selectedAdult) {
					newSelectedIndex = i - this.roomMinAdults;
				}
			}
			adults.selectedIndex = newSelectedIndex;
		}
	       
		var children = this.form["child[" + i + "]"];
		if (children && children.options.length != this.roomMaxChildren + 1) {
			var selectedChild = children.selectedIndex;
			if (selectedChild > this.roomMaxChildren) {
				selectedChild = 0;
			}
			children.options.length = this.roomMaxChildren + 1;
			for (i = 0 ; i <= this.roomMaxChildren; ++i) {
				children.options[i] = new Option(i, i);
			}
			children.selectedIndex = selectedChild;
		}
	},
	
	displayElementForRoomType: function (title){
		$(title + ".condo").style.display = 'none';
		$(title + ".hotel").style.display = 'none';
		$(title + "." + this.roomType).style.display = '';
	},
	
	isGroup: function() {
		return (this.form["numrooms"].value > this.cfg.maxRooms);
	},
	
	update: function() {
		this.renderUi();
		this.selectRoomType();
		this.displayElementForRoomType("roomTitle");
		       
		if (!this.isGroup()){        
			$('adultLabel').style.display = '';
			$('childLabel').style.display = '';
		} else {        
			$('adultLabel').style.display = 'none';
			$('childLabel').style.display = 'none';
		}
		
		this.showRooms();
	},
	
	showRooms: function() {
		var form = this.form;
		this.numRooms = this.cfg.inputNumRooms.value;
		
		var dispRooms = this.numRooms;
		if (this.numRooms > this.cfg.maxRooms) {
			dispRooms = 0;
			if (this.cfg.divTotalAdults) {
				this.cfg.divTotalAdults.style.visibility = "hidden";
			}
			if (this.cfg.divTotalChildren) {
				this.cfg.divTotalChildren.style.visibility = "hidden";
			}
		} else {
			if (this.cfg.divTotalAdults) {
				this.cfg.divTotalAdults.style.visibility = "visible";
			}
			if (this.cfg.divTotalChildren) {
				this.cfg.divTotalChildren.style.visibility = "visible";
			}
		}
		
		for (var i = 0; i < this.numRooms; i++) {
			YAHOO.util.Dom.addClass($("room["+i+"]"), this.rowStyles[i % 2]);
			this.updateAdultChildDropdowns(i)
			if (i < dispRooms) {
				var childs = parseInt(form["child[" + i + "]"].value);
				$("room["+i+"]").style.display = "block";
				for (var j = 0; j < this.cfg.maxChildren; j++) {
					var caid = "OIdx["+i+"].CAIdx["+j+"]";
					if (j < childs) {
						$(caid).style.display = "block";
						form[caid].disabled = false;
					} else {
						$(caid).selectedIndex = 0;
						$(caid).style.display = "none";
						form[caid].disabled = true;
					}
				}
				if (childs > 0) {
					YAHOO.util.Dom.addClass($("child[" + i + "]"), "child-dropdown-on");
					$("child[" + i + "]-ages").style.display = "block";
				} else {
					YAHOO.util.Dom.removeClass(form["child[" + i + "]"], "child-dropdown-on");
					$("child[" + i + "]-ages").style.display = "none";
				}
			} else {
				$("room["+i+"]").style.display = "none";
			}
		}
		this.calcTotals();
	},
	
	calcTotals: function() {
		var form = this.form
		var adultTotal = 0;
		var childTotal = 0;
	
		if (this.numRooms <= this.cfg.maxRooms) {
			for (i = 0; i < this.numRooms; i++) {
				var tmp = form["adults[" + i + "]"];
				adultTotal += parseInt(tmp.options[tmp.selectedIndex].value);
			}
			
			for (i = 0; i < this.numRooms; i++) {
				var tmp = form["child[" + i + "]"];
				childTotal += parseInt(tmp.options[tmp.selectedIndex].value);
			}
		               
			this.cfg.inputTotalAdults.innerHTML = adultTotal;
			this.cfg.inputTotalChildren.innerHTML = childTotal;
		}
	},
	
	toggle: function() {
		if (this.showing) {
			this.hide();
		} else {
			this.show();
		}
	},
	
	show: function() {
		this.numRooms = this.cfg.inputNumRooms.value;
		if (!this.showing && (this.numRooms <= this.cfg.maxRooms)) {
			YAHOO.util.Dom.addClass(this.cfg.dropDownAnchor, "roomsGuestActive");
			this.positionAnchor();
			YAHOO.util.Dom.addClass($(this.cfg.roomsCloseId), "roomsActive");
			$(this.cfg.roomsCloseId).style.display = "block";
			this.positionCloseButton();
			this.dropdown.show();
			this.showing = true;
		}
	},
		
	hide: function() {
		if (this.showing) {
			YAHOO.util.Dom.removeClass(this.cfg.dropDownAnchor, "roomsGuestActive");
			YAHOO.util.Dom.removeClass($(this.cfg.roomsCloseId), "roomsActive");
			this.dropdown.hide();
			this.showing = false;
		}
	},
	
	isTarget: function(evt) {
		var target = YAHOO.util.Event.getTarget(evt);
		return target == this.cfg.inputNumRooms 
			|| target == this.cfg.divGuest
			|| target == this.cfg.inputTotalAdults
			|| target == this.cfg.inputTotalChildren 
			|| YAHOO.util.Dom.isAncestor(this.cfg.divGuest, target);
	},
	
	setMaxMin: function() {
		this.maxGuest = 0;
		this.minAdults = 100;
		var propInfo = this.cfg.propInfo;
		for (var i = 0; i < propInfo.length; i++) {
			var prop = propInfo[i];
			if ($(prop.nameKey) && $(prop.nameKey).checked) {
				if (this.maxGuest < prop.maxGuest) this.maxGuest = prop.maxGuest;
				if (this.minAdults > prop.minAdults) this.minAdults = prop.minAdults;
			}
		}
	},
		
	getBoxValue: function(box) {
		if (box.type == "hidden") {
			return "false" != box.value;
		} else {
			return box.checked;
		}
	},
	
	selectRoomType: function() {
		this.roomType = "hotel";
		this.roomMaxGuests = 8;
		this.roomMinAdults = 1;
		this.roomMaxChildren = 8;
		var form = this.form;
		
		if (form.isPropertyPage != null && form.isPropertyPage.value == "true") {
			if (form.isCondo != null && form.isCondo == "true") {
				this.roomMaxGuests = 16;
				this.roomType = "condo";
			} else {
				this.roomType = "hotel";
		    }
		} else {
			var box = form.allPropertyTypesSelected;
			var val;
			if (box) {
				val = this.getBoxValue(box);
				var propInfo = this.cfg.propInfo;
				if (val) {				
	           		for (var i=0 ; i < propInfo.length; ++i) {
	           			if ("text.property.hotelPropertyType" == propInfo[i].nameKey 
	           				|| "text.property.resortPropertyType" == propInfo[i].nameKey) {
							this.roomType = "hotel";
							this.roomMaxGuests = propInfo[i].maxGuest;
							this.roomMinAdults = propInfo[i].minAdults;
							break;
						}
					}
				} else {
					var typeStop = false;
					for (var i=0 ; i < propInfo.length ; ++i) {
						box = $(propInfo[i].nameKey)
	               		if (box) {
	               			val = this.getBoxValue(box);
						if (val) {
								if (propInfo[i].type != "condo") {
									roomMinAdults = propInfo[i].minAdults;
									}
	                       
								if (!typeStop) {
									roomType = propInfo[i].type;
									if (roomType == "hotel") {
										typeStop = true;
									}
								}
	                       
								if (this.roomMaxGuests < propInfo[i].maxGuest) {
									this.roomMaxGuests = propInfo[i].maxGuest;
								}
							
								if (propInfo[i].type == "hotel" && this.roomMinAdults < propInfo[i].minAdults) {
									this.roomMinAdults = propInfo[i].minAdults;
								}
							}
						}
					}
				}
			}
		}
		if (form.isPropertyPage != null && form.isPropertyPage.value == "true") {
			if(form.isCondo != null &&  form.isCondo.value == "true") {
				this.roomType = "condo";
			} else {
				this.roomType = "hotel";
			}
		}
	},
	
	renderUi: function () {
		var rooms = this.cfg.inputNumRooms.value;
		var roomGrids = YAHOO.util.Dom.getElementsByClassName("room-grid", "div", $("collect"))
		if (roomGrids.length < rooms && rooms <= this.cfg.maxRooms) {
			// Make more room grids.
			this.addRoomGrids(roomGrids, rooms);
		} else {
			// Delete extra room grids.
			this.deleteRoomGrids(roomGrids, roomGrids.length - rooms);
		}
	},

	deleteRoomGrids: function(grids, count) {
		for (var i = grids.length - 1; i >= (grids.length - count); i--) {
			$('collect').removeChild(grids[i]);
		}
	},

	addRoomGrids: function(grids, rooms) {
		var form = this.form;
		var collect = $('collect');
		for (var i = grids.length; i < rooms; i++) {
			var grid = document.createElement("div")
			grid.id = "room["+i+"]";
			YAHOO.util.Dom.addClass(grid, "room-grid");
			
			var html = '<div class="room-num"><span>Room '+(i+1)+'</span></div>';
			html += '<div class="adult-dropdown">'
			html += '<select name="adults['+i+']" style="width:40px">'
			for (var j=1; j <= this.cfg.maxAdults; j++ ) {
				html += '<option value="'+j+'">'+j+'</option>'
			}
			html += '</select>'
			html += '</div>'
			
			var childId = 'child['+i+']';			
				
			html += '<div class="child-dropdown" id="'+childId+'">'
			html += '<select name="'+childId+'" style="width: 40px;">';
			for (j=0; j < this.cfg.maxChildren; j++) {
				html += '<option value="'+j+'">'+j+'</option>'
			}
			html += '</select>'
			html += '</div>'
			
			html += '<div id="'+childId+'-ages" class="child-ages">';
			for (j=-1; j < this.cfg.maxChildren; j++) {
				html += '<div id="OIdx['+i+'].CAIdx['+j+']" class="child-age">'
				html += '<label style="font-weight: normal; vertical-align: middle;">Age of Child ' + (j + 1) + ':</label>';
				html += '<select name="OIdx['+i+'].CAIdx['+j+']" id="OIdx['+i+'].CAIdx['+j+']" disabled>'
				for (var k = -1; k < 19; k++) {
					var lab = k;
					if (k == -1) lab = '--';
					if (k == 0) lab = '<1';
					html += '<option value="'+k+'">'+lab+'</option>'
				}
				html += '</select>'
				html += '<br/></div>'
			}
			html += '</div>'
			grid.innerHTML = html;
			collect.appendChild(grid);

			YAHOO.util.Event.on(
				form["adults[" + i + "]"],"change", 
				function(){
					this.calcTotals();
					Hotels.ClickTrack.click("Guest-Change");
				}, this, true);
				
			form["adults[" + i + "]"].selectedIndex = 1;
			
			YAHOO.util.Event.on(
				form["child[" + i + "]"],"change", 
			 	function() {
					this.update(); 
					this.calcTotals();
					Hotels.ClickTrack.click("Guest-Change");
				}, this, true);
		}
	}
});

