﻿/*
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
*/
(function() {

    /**
    * Config is a utility used within an Object to allow the implementer to
    * maintain a list of local configuration properties and listen for changes 
    * to those properties dynamically using CustomEvent. The initial values are 
    * also maintained so that the configuration can be reset at any given point 
    * to its initial state.
    * @namespace YAHOO.util
    * @class Config
    * @constructor
    * @param {Object} owner The owner Object to which this Config Object belongs
    */
    YAHOO.util.Config = function(owner) {

        if (owner) {
            this.init(owner);
        }


    };


    var Lang = YAHOO.lang,
        CustomEvent = YAHOO.util.CustomEvent,
        Config = YAHOO.util.Config;


    /**
    * Constant representing the CustomEvent type for the config changed event.
    * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
    * @private
    * @static
    * @final
    */
    Config.CONFIG_CHANGED_EVENT = "configChanged";

    /**
    * Constant representing the boolean type string
    * @property YAHOO.util.Config.BOOLEAN_TYPE
    * @private
    * @static
    * @final
    */
    Config.BOOLEAN_TYPE = "boolean";

    Config.prototype = {

        /**
        * Object reference to the owner of this Config Object
        * @property owner
        * @type Object
        */
        owner: null,

        /**
        * Boolean flag that specifies whether a queue is currently 
        * being executed
        * @property queueInProgress
        * @type Boolean
        */
        queueInProgress: false,

        /**
        * Maintains the local collection of configuration property objects and 
        * their specified values
        * @property config
        * @private
        * @type Object
        */
        config: null,

        /**
        * Maintains the local collection of configuration property objects as 
        * they were initially applied.
        * This object is used when resetting a property.
        * @property initialConfig
        * @private
        * @type Object
        */
        initialConfig: null,

        /**
        * Maintains the local, normalized CustomEvent queue
        * @property eventQueue
        * @private
        * @type Object
        */
        eventQueue: null,

        /**
        * Custom Event, notifying subscribers when Config properties are set 
        * (setProperty is called without the silent flag
        * @event configChangedEvent
        */
        configChangedEvent: null,

        /**
        * Initializes the configuration Object and all of its local members.
        * @method init
        * @param {Object} owner The owner Object to which this Config 
        * Object belongs
        */
        init: function(owner) {

            this.owner = owner;

            this.configChangedEvent =
                this.createEvent(Config.CONFIG_CHANGED_EVENT);

            this.configChangedEvent.signature = CustomEvent.LIST;
            this.queueInProgress = false;
            this.config = {};
            this.initialConfig = {};
            this.eventQueue = [];

        },

        /**
        * Validates that the value passed in is a Boolean.
        * @method checkBoolean
        * @param {Object} val The value to validate
        * @return {Boolean} true, if the value is valid
        */
        checkBoolean: function(val) {
            return (typeof val == Config.BOOLEAN_TYPE);
        },

        /**
        * Validates that the value passed in is a number.
        * @method checkNumber
        * @param {Object} val The value to validate
        * @return {Boolean} true, if the value is valid
        */
        checkNumber: function(val) {
            return (!isNaN(val));
        },

        /**
        * Fires a configuration property event using the specified value. 
        * @method fireEvent
        * @private
        * @param {String} key The configuration property's name
        * @param {value} Object The value of the correct type for the property
        */
        fireEvent: function(key, value) {
            var property = this.config[key];

            if (property && property.event) {
                property.event.fire(value);
            }
        },

        /**
        * Adds a property to the Config Object's private config hash.
        * @method addProperty
        * @param {String} key The configuration property's name
        * @param {Object} propertyObject The Object containing all of this 
        * property's arguments
        */
        addProperty: function(key, propertyObject) {
            key = key.toLowerCase();

            this.config[key] = propertyObject;

            propertyObject.event = this.createEvent(key, { scope: this.owner });
            propertyObject.event.signature = CustomEvent.LIST;


            propertyObject.key = key;

            if (propertyObject.handler) {
                propertyObject.event.subscribe(propertyObject.handler,
                    this.owner);
            }

            this.setProperty(key, propertyObject.value, true);

            if (!propertyObject.suppressEvent) {
                this.queueProperty(key, propertyObject.value);
            }

        },

        /**
        * Returns a key-value configuration map of the values currently set in  
        * the Config Object.
        * @method getConfig
        * @return {Object} The current config, represented in a key-value map
        */
        getConfig: function() {

            var cfg = {},
                currCfg = this.config,
                prop,
                property;

            for (prop in currCfg) {
                if (Lang.hasOwnProperty(currCfg, prop)) {
                    property = currCfg[prop];
                    if (property && property.event) {
                        cfg[prop] = property.value;
                    }
                }
            }

            return cfg;
        },

        /**
        * Returns the value of specified property.
        * @method getProperty
        * @param {String} key The name of the property
        * @return {Object}  The value of the specified property
        */
        getProperty: function(key) {
            var property = this.config[key.toLowerCase()];
            if (property && property.event) {
                return property.value;
            } else {
                return undefined;
            }
        },

        /**
        * Resets the specified property's value to its initial value.
        * @method resetProperty
        * @param {String} key The name of the property
        * @return {Boolean} True is the property was reset, false if not
        */
        resetProperty: function(key) {

            key = key.toLowerCase();

            var property = this.config[key];

            if (property && property.event) {

                if (this.initialConfig[key] &&
                    !Lang.isUndefined(this.initialConfig[key])) {

                    this.setProperty(key, this.initialConfig[key]);

                    return true;

                }

            } else {

                return false;
            }

        },

        /**
        * Sets the value of a property. If the silent property is passed as 
        * true, the property's event will not be fired.
        * @method setProperty
        * @param {String} key The name of the property
        * @param {String} value The value to set the property to
        * @param {Boolean} silent Whether the value should be set silently, 
        * without firing the property event.
        * @return {Boolean} True, if the set was successful, false if it failed.
        */
        setProperty: function(key, value, silent) {

            var property;

            key = key.toLowerCase();

            if (this.queueInProgress && !silent) {
                // Currently running through a queue... 
                this.queueProperty(key, value);
                return true;

            } else {
                property = this.config[key];
                if (property && property.event) {
                    if (property.validator && !property.validator(value)) {
                        return false;
                    } else {
                        property.value = value;
                        if (!silent) {
                            this.fireEvent(key, value);
                            this.configChangedEvent.fire([key, value]);
                        }
                        return true;
                    }
                } else {
                    return false;
                }
            }
        },

        /**
        * Sets the value of a property and queues its event to execute. If the 
        * event is already scheduled to execute, it is
        * moved from its current position to the end of the queue.
        * @method queueProperty
        * @param {String} key The name of the property
        * @param {String} value The value to set the property to
        * @return {Boolean}  true, if the set was successful, false if 
        * it failed.
        */
        queueProperty: function(key, value) {

            key = key.toLowerCase();

            var property = this.config[key],
                foundDuplicate = false,
                iLen,
                queueItem,
                queueItemKey,
                queueItemValue,
                sLen,
                supercedesCheck,
                qLen,
                queueItemCheck,
                queueItemCheckKey,
                queueItemCheckValue,
                i,
                s,
                q;

            if (property && property.event) {

                if (!Lang.isUndefined(value) && property.validator &&
                    !property.validator(value)) { // validator
                    return false;
                } else {

                    if (!Lang.isUndefined(value)) {
                        property.value = value;
                    } else {
                        value = property.value;
                    }

                    foundDuplicate = false;
                    iLen = this.eventQueue.length;

                    for (i = 0; i < iLen; i++) {
                        queueItem = this.eventQueue[i];

                        if (queueItem) {
                            queueItemKey = queueItem[0];
                            queueItemValue = queueItem[1];

                            if (queueItemKey == key) {

                                /*
                                found a dupe... push to end of queue, null 
                                current item, and break
                                */

                                this.eventQueue[i] = null;

                                this.eventQueue.push(
                                    [key, (!Lang.isUndefined(value) ?
                                    value : queueItemValue)]);

                                foundDuplicate = true;
                                break;
                            }
                        }
                    }

                    // this is a refire, or a new property in the queue

                    if (!foundDuplicate && !Lang.isUndefined(value)) {
                        this.eventQueue.push([key, value]);
                    }
                }

                if (property.supercedes) {

                    sLen = property.supercedes.length;

                    for (s = 0; s < sLen; s++) {

                        supercedesCheck = property.supercedes[s];
                        qLen = this.eventQueue.length;

                        for (q = 0; q < qLen; q++) {
                            queueItemCheck = this.eventQueue[q];

                            if (queueItemCheck) {
                                queueItemCheckKey = queueItemCheck[0];
                                queueItemCheckValue = queueItemCheck[1];

                                if (queueItemCheckKey ==
                                    supercedesCheck.toLowerCase()) {

                                    this.eventQueue.push([queueItemCheckKey,
                                        queueItemCheckValue]);

                                    this.eventQueue[q] = null;
                                    break;

                                }
                            }
                        }
                    }
                }


                return true;
            } else {
                return false;
            }
        },

        /**
        * Fires the event for a property using the property's current value.
        * @method refireEvent
        * @param {String} key The name of the property
        */
        refireEvent: function(key) {

            key = key.toLowerCase();

            var property = this.config[key];

            if (property && property.event &&

                !Lang.isUndefined(property.value)) {

                if (this.queueInProgress) {

                    this.queueProperty(key);

                } else {

                    this.fireEvent(key, property.value);

                }

            }
        },

        /**
        * Applies a key-value Object literal to the configuration, replacing  
        * any existing values, and queueing the property events.
        * Although the values will be set, fireQueue() must be called for their 
        * associated events to execute.
        * @method applyConfig
        * @param {Object} userConfig The configuration Object literal
        * @param {Boolean} init  When set to true, the initialConfig will 
        * be set to the userConfig passed in, so that calling a reset will 
        * reset the properties to the passed values.
        */
        applyConfig: function(userConfig, init) {

            var sKey,
                oConfig;

            if (init) {
                oConfig = {};
                for (sKey in userConfig) {
                    if (Lang.hasOwnProperty(userConfig, sKey)) {
                        oConfig[sKey.toLowerCase()] = userConfig[sKey];
                    }
                }
                this.initialConfig = oConfig;
            }

            for (sKey in userConfig) {
                if (Lang.hasOwnProperty(userConfig, sKey)) {
                    this.queueProperty(sKey, userConfig[sKey]);
                }
            }
        },

        /**
        * Refires the events for all configuration properties using their 
        * current values.
        * @method refresh
        */
        refresh: function() {

            var prop;

            for (prop in this.config) {
                if (Lang.hasOwnProperty(this.config, prop)) {
                    this.refireEvent(prop);
                }
            }
        },

        /**
        * Fires the normalized list of queued property change events
        * @method fireQueue
        */
        fireQueue: function() {

            var i,
                queueItem,
                key,
                value,
                property;

            this.queueInProgress = true;
            for (i = 0; i < this.eventQueue.length; i++) {
                queueItem = this.eventQueue[i];
                if (queueItem) {

                    key = queueItem[0];
                    value = queueItem[1];
                    property = this.config[key];

                    property.value = value;

                    this.fireEvent(key, value);
                }
            }

            this.queueInProgress = false;
            this.eventQueue = [];
        },

        /**
        * Subscribes an external handler to the change event for any 
        * given property. 
        * @method subscribeToConfigEvent
        * @param {String} key The property name
        * @param {Function} handler The handler function to use subscribe to 
        * the property's event
        * @param {Object} obj The Object to use for scoping the event handler 
        * (see CustomEvent documentation)
        * @param {Boolean} override Optional. If true, will override "this"  
        * within the handler to map to the scope Object passed into the method.
        * @return {Boolean} True, if the subscription was successful, 
        * otherwise false.
        */
        subscribeToConfigEvent: function(key, handler, obj, override) {

            var property = this.config[key.toLowerCase()];

            if (property && property.event) {
                if (!Config.alreadySubscribed(property.event, handler, obj)) {
                    property.event.subscribe(handler, obj, override);
                }
                return true;
            } else {
                return false;
            }

        },

        /**
        * Unsubscribes an external handler from the change event for any 
        * given property. 
        * @method unsubscribeFromConfigEvent
        * @param {String} key The property name
        * @param {Function} handler The handler function to use subscribe to 
        * the property's event
        * @param {Object} obj The Object to use for scoping the event 
        * handler (see CustomEvent documentation)
        * @return {Boolean} True, if the unsubscription was successful, 
        * otherwise false.
        */
        unsubscribeFromConfigEvent: function(key, handler, obj) {
            var property = this.config[key.toLowerCase()];
            if (property && property.event) {
                return property.event.unsubscribe(handler, obj);
            } else {
                return false;
            }
        },

        /**
        * Returns a string representation of the Config object
        * @method toString
        * @return {String} The Config object in string format.
        */
        toString: function() {
            var output = "Config";
            if (this.owner) {
                output += " [" + this.owner.toString() + "]";
            }
            return output;
        },

        /**
        * Returns a string representation of the Config object's current 
        * CustomEvent queue
        * @method outputEventQueue
        * @return {String} The string list of CustomEvents currently queued 
        * for execution
        */
        outputEventQueue: function() {

            var output = "",
                queueItem,
                q,
                nQueue = this.eventQueue.length;

            for (q = 0; q < nQueue; q++) {
                queueItem = this.eventQueue[q];
                if (queueItem) {
                    output += queueItem[0] + "=" + queueItem[1] + ", ";
                }
            }
            return output;
        },

        /**
        * Sets all properties to null, unsubscribes all listeners from each 
        * property's change event and all listeners from the configChangedEvent.
        * @method destroy
        */
        destroy: function() {

            var oConfig = this.config,
                sProperty,
                oProperty;


            for (sProperty in oConfig) {

                if (Lang.hasOwnProperty(oConfig, sProperty)) {

                    oProperty = oConfig[sProperty];

                    oProperty.event.unsubscribeAll();
                    oProperty.event = null;

                }

            }

            this.configChangedEvent.unsubscribeAll();

            this.configChangedEvent = null;
            this.owner = null;
            this.config = null;
            this.initialConfig = null;
            this.eventQueue = null;

        }

    };



    /**
    * Checks to determine if a particular function/Object pair are already 
    * subscribed to the specified CustomEvent
    * @method YAHOO.util.Config.alreadySubscribed
    * @static
    * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check 
    * the subscriptions
    * @param {Function} fn The function to look for in the subscribers list
    * @param {Object} obj The execution scope Object for the subscription
    * @return {Boolean} true, if the function/Object pair is already subscribed 
    * to the CustomEvent passed in
    */
    Config.alreadySubscribed = function(evt, fn, obj) {

        var nSubscribers = evt.subscribers.length,
            subsc,
            i;

        if (nSubscribers > 0) {
            i = nSubscribers - 1;
            do {
                subsc = evt.subscribers[i];
                if (subsc && subsc.obj == obj && subsc.fn == fn) {
                    return true;
                }
            }
            while (i--);
        }

        return false;

    };

    YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);

} ());

/**
* YAHOO.widget.DateMath is used for simple date manipulation. The class is a static utility
* used for adding, subtracting, and comparing dates.
* @namespace YAHOO.widget
* @class DateMath
*/
YAHOO.widget.DateMath = {
    /**
    * Constant field representing Day
    * @property DAY
    * @static
    * @final
    * @type String
    */
    DAY: "D",

    /**
    * Constant field representing Week
    * @property WEEK
    * @static
    * @final
    * @type String
    */
    WEEK: "W",

    /**
    * Constant field representing Year
    * @property YEAR
    * @static
    * @final
    * @type String
    */
    YEAR: "Y",

    /**
    * Constant field representing Month
    * @property MONTH
    * @static
    * @final
    * @type String
    */
    MONTH: "M",

    /**
    * Constant field representing one day, in milliseconds
    * @property ONE_DAY_MS
    * @static
    * @final
    * @type Number
    */
    ONE_DAY_MS: 1000 * 60 * 60 * 24,

    /**
    * Constant field representing the date in first week of January
    * which identifies the first week of the year.
    * <p>
    * In the U.S, Jan 1st is normally used based on a Sunday start of week.
    * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
    * </p>
    * @property WEEK_ONE_JAN_DATE
    * @static
    * @type Number
    */
    WEEK_ONE_JAN_DATE: 1,

    /**
    * Adds the specified amount of time to the this instance.
    * @method add
    * @param {Date} date	The JavaScript Date object to perform addition on
    * @param {String} field	The field constant to be used for performing addition.
    * @param {Number} amount	The number of units (measured in the field constant) to add to the date.
    * @return {Date} The resulting Date object
    */
    add: function(date, field, amount) {
        var d = new Date(date.getTime());
        switch (field) {
            case this.MONTH:
                var newMonth = date.getMonth() + amount;
                var years = 0;

                if (newMonth < 0) {
                    while (newMonth < 0) {
                        newMonth += 12;
                        years -= 1;
                    }
                } else if (newMonth > 11) {
                    while (newMonth > 11) {
                        newMonth -= 12;
                        years += 1;
                    }
                }

                d.setMonth(newMonth);
                d.setFullYear(date.getFullYear() + years);
                break;
            case this.DAY:
                this._addDays(d, amount);
                // d.setDate(date.getDate() + amount);
                break;
            case this.YEAR:
                d.setFullYear(date.getFullYear() + amount);
                break;
            case this.WEEK:
                this._addDays(d, (amount * 7));
                // d.setDate(date.getDate() + (amount * 7));
                break;
        }
        return d;
    },

    /**
    * Private helper method to account for bug in Safari 2 (webkit < 420)
    * when Date.setDate(n) is called with n less than -128 or greater than 127.
    * <p>
    * Fix approach and original findings are available here:
    * http://brianary.blogspot.com/2006/03/safari-date-bug.html
    * </p>
    * @method _addDays
    * @param {Date} d JavaScript date object
    * @param {Number} nDays The number of days to add to the date object (can be negative)
    * @private
    */
    _addDays: function(d, nDays) {
        if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420) {
            if (nDays < 0) {
                // Ensure we don't go below -128 (getDate() is always 1 to 31, so we won't go above 127)
                for (var min = -128; nDays < min; nDays -= min) {
                    d.setDate(d.getDate() + min);
                }
            } else {
                // Ensure we don't go above 96 + 31 = 127
                for (var max = 96; nDays > max; nDays -= max) {
                    d.setDate(d.getDate() + max);
                }
            }
            // nDays should be remainder between -128 and 96
        }
        d.setDate(d.getDate() + nDays);
    },

    /**
    * Subtracts the specified amount of time from the this instance.
    * @method subtract
    * @param {Date} date	The JavaScript Date object to perform subtraction on
    * @param {Number} field	The this field constant to be used for performing subtraction.
    * @param {Number} amount	The number of units (measured in the field constant) to subtract from the date.
    * @return {Date} The resulting Date object
    */
    subtract: function(date, field, amount) {
        return this.add(date, field, (amount * -1));
    },

    /**
    * Determines whether a given date is before another date on the calendar.
    * @method before
    * @param {Date} date		The Date object to compare with the compare argument
    * @param {Date} compareTo	The Date object to use for the comparison
    * @return {Boolean} true if the date occurs before the compared date; false if not.
    */
    before: function(date, compareTo) {
        var ms = compareTo.getTime();
        if (date.getTime() < ms) {
            return true;
        } else {
            return false;
        }
    },

    /**
    * Determines whether a given date is after another date on the calendar.
    * @method after
    * @param {Date} date		The Date object to compare with the compare argument
    * @param {Date} compareTo	The Date object to use for the comparison
    * @return {Boolean} true if the date occurs after the compared date; false if not.
    */
    after: function(date, compareTo) {
        var ms = compareTo.getTime();
        if (date.getTime() > ms) {
            return true;
        } else {
            return false;
        }
    },

    /**
    * Determines whether a given date is between two other dates on the calendar.
    * @method between
    * @param {Date} date		The date to check for
    * @param {Date} dateBegin	The start of the range
    * @param {Date} dateEnd		The end of the range
    * @return {Boolean} true if the date occurs between the compared dates; false if not.
    */
    between: function(date, dateBegin, dateEnd) {
        if (this.after(date, dateBegin) && this.before(date, dateEnd)) {
            return true;
        } else {
            return false;
        }
    },

    /**
    * Retrieves a JavaScript Date object representing January 1 of any given year.
    * @method getJan1
    * @param {Number} calendarYear		The calendar year for which to retrieve January 1
    * @return {Date}	January 1 of the calendar year specified.
    */
    getJan1: function(calendarYear) {
        return this.getDate(calendarYear, 0, 1);
    },

    /**
    * Calculates the number of days the specified date is from January 1 of the specified calendar year.
    * Passing January 1 to this function would return an offset value of zero.
    * @method getDayOffset
    * @param {Date}	date	The JavaScript date for which to find the offset
    * @param {Number} calendarYear	The calendar year to use for determining the offset
    * @return {Number}	The number of days since January 1 of the given year
    */
    getDayOffset: function(date, calendarYear) {
        var beginYear = this.getJan1(calendarYear); // Find the start of the year. This will be in week 1.

        // Find the number of days the passed in date is away from the calendar year start
        var dayOffset = Math.ceil((date.getTime() - beginYear.getTime()) / this.ONE_DAY_MS);
        return dayOffset;
    },

    /**
    * Calculates the week number for the given date. Can currently support standard
    * U.S. week numbers, based on Jan 1st defining the 1st week of the year, and 
    * ISO8601 week numbers, based on Jan 4th defining the 1st week of the year.
    * 
    * @method getWeekNumber
    * @param {Date}	date The JavaScript date for which to find the week number
    * @param {Number} firstDayOfWeek The index of the first day of the week (0 = Sun, 1 = Mon ... 6 = Sat).
    * Defaults to 0
    * @param {Number} janDate The date in the first week of January which defines week one for the year
    * Defaults to the value of YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE, which is 1 (Jan 1st). 
    * For the U.S, this is normally Jan 1st. ISO8601 uses Jan 4th to define the first week of the year.
    * 
    * @return {Number} The number of the week containing the given date.
    */
    getWeekNumber: function(date, firstDayOfWeek, janDate) {

        // Setup Defaults
        firstDayOfWeek = firstDayOfWeek || 0;
        janDate = janDate || this.WEEK_ONE_JAN_DATE;

        var targetDate = this.clearTime(date),
			startOfWeek,
			endOfWeek;

        if (targetDate.getDay() === firstDayOfWeek) {
            startOfWeek = targetDate;
        } else {
            startOfWeek = this.getFirstDayOfWeek(targetDate, firstDayOfWeek);
        }

        var startYear = startOfWeek.getFullYear(),
			startTime = startOfWeek.getTime();

        // DST shouldn't be a problem here, math is quicker than setDate();
        endOfWeek = new Date(startOfWeek.getTime() + 6 * this.ONE_DAY_MS);

        var weekNum;
        if (startYear !== endOfWeek.getFullYear() && endOfWeek.getDate() >= janDate) {
            // If years don't match, endOfWeek is in Jan. and if the 
            // week has WEEK_ONE_JAN_DATE in it, it's week one by definition.
            weekNum = 1;
        } else {
            // Get the 1st day of the 1st week, and 
            // find how many days away we are from it.
            var weekOne = this.clearTime(this.getDate(startYear, 0, janDate)),
				weekOneDayOne = this.getFirstDayOfWeek(weekOne, firstDayOfWeek);

            // Round days to smoothen out 1 hr DST diff
            var daysDiff = Math.round((targetDate.getTime() - weekOneDayOne.getTime()) / this.ONE_DAY_MS);

            // Calc. Full Weeks
            var rem = daysDiff % 7;
            var weeksDiff = (daysDiff - rem) / 7;
            weekNum = weeksDiff + 1;
        }
        return weekNum;
    },

    /**
    * Get the first day of the week, for the give date. 
    * @param {Date} dt The date in the week for which the first day is required.
    * @param {Number} startOfWeek The index for the first day of the week, 0 = Sun, 1 = Mon ... 6 = Sat (defaults to 0)
    * @return {Date} The first day of the week
    */
    getFirstDayOfWeek: function(dt, startOfWeek) {
        startOfWeek = startOfWeek || 0;
        var dayOfWeekIndex = dt.getDay(),
			dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;

        return this.subtract(dt, this.DAY, dayOfWeek);
    },

    /**
    * Determines if a given week overlaps two different years.
    * @method isYearOverlapWeek
    * @param {Date}	weekBeginDate	The JavaScript Date representing the first day of the week.
    * @return {Boolean}	true if the date overlaps two different years.
    */
    isYearOverlapWeek: function(weekBeginDate) {
        var overlaps = false;
        var nextWeek = this.add(weekBeginDate, this.DAY, 6);
        if (nextWeek.getFullYear() != weekBeginDate.getFullYear()) {
            overlaps = true;
        }
        return overlaps;
    },

    /**
    * Determines if a given week overlaps two different months.
    * @method isMonthOverlapWeek
    * @param {Date}	weekBeginDate	The JavaScript Date representing the first day of the week.
    * @return {Boolean}	true if the date overlaps two different months.
    */
    isMonthOverlapWeek: function(weekBeginDate) {
        var overlaps = false;
        var nextWeek = this.add(weekBeginDate, this.DAY, 6);
        if (nextWeek.getMonth() != weekBeginDate.getMonth()) {
            overlaps = true;
        }
        return overlaps;
    },

    /**
    * Gets the first day of a month containing a given date.
    * @method findMonthStart
    * @param {Date}	date	The JavaScript Date used to calculate the month start
    * @return {Date}		The JavaScript Date representing the first day of the month
    */
    findMonthStart: function(date) {
        var start = this.getDate(date.getFullYear(), date.getMonth(), 1);
        return start;
    },

    /**
    * Gets the last day of a month containing a given date.
    * @method findMonthEnd
    * @param {Date}	date	The JavaScript Date used to calculate the month end
    * @return {Date}		The JavaScript Date representing the last day of the month
    */
    findMonthEnd: function(date) {
        var start = this.findMonthStart(date);
        var nextMonth = this.add(start, this.MONTH, 1);
        var end = this.subtract(nextMonth, this.DAY, 1);
        return end;
    },

    /**
    * Clears the time fields from a given date, effectively setting the time to 12 noon.
    * @method clearTime
    * @param {Date}	date	The JavaScript Date for which the time fields will be cleared
    * @return {Date}		The JavaScript Date cleared of all time fields
    */
    clearTime: function(date) {
        date.setHours(12, 0, 0, 0);
        return date;
    },

    /**
    * Returns a new JavaScript Date object, representing the given year, month and date. Time fields (hr, min, sec, ms) on the new Date object
    * are set to 0. The method allows Date instances to be created with the a year less than 100. "new Date(year, month, date)" implementations 
    * set the year to 19xx if a year (xx) which is less than 100 is provided.
    * <p>
    * <em>NOTE:</em>Validation on argument values is not performed. It is the caller's responsibility to ensure
    * arguments are valid as per the ECMAScript-262 Date object specification for the new Date(year, month[, date]) constructor.
    * </p>
    * @method getDate
    * @param {Number} y Year.
    * @param {Number} m Month index from 0 (Jan) to 11 (Dec).
    * @param {Number} d (optional) Date from 1 to 31. If not provided, defaults to 1.
    * @return {Date} The JavaScript date object with year, month, date set as provided.
    */
    getDate: function(y, m, d) {
        var dt = null;
        if (YAHOO.lang.isUndefined(d)) {
            d = 1;
        }
        if (y >= 100) {
            dt = new Date(y, m, d);
        } else {
            dt = new Date();
            dt.setFullYear(y);
            dt.setMonth(m);
            dt.setDate(d);
            dt.setHours(0, 0, 0, 0);
        }
        return dt;
    }
};

/**
* The Calendar component is a UI control that enables users to choose one or more dates from a graphical calendar presented in a one-month or
* multi-month interface. Calendars are generated entirely via script and can be navigated without any page refreshes.
* @module    calendar
* @title    Calendar
* @namespace  YAHOO.widget
* @requires  yahoo,dom,event
*/
(function() {

    var Dom = YAHOO.util.Dom,
		Event = YAHOO.util.Event,
		Lang = YAHOO.lang,
		DateMath = YAHOO.widget.DateMath;

    /**
    * Calendar is the base class for the Calendar widget. In its most basic
    * implementation, it has the ability to render a calendar widget on the page
    * that can be manipulated to select a single date, move back and forth between
    * months and years.
    * <p>To construct the placeholder for the calendar widget, the code is as
    * follows:
    *	<xmp>
    *		<div id="calContainer"></div>
    *	</xmp>
    * </p>
    * <p>
    * <strong>NOTE: As of 2.4.0, the constructor's ID argument is optional.</strong>
    * The Calendar can be constructed by simply providing a container ID string, 
    * or a reference to a container DIV HTMLElement (the element needs to exist 
    * in the document).
    * 
    * E.g.:
    *	<xmp>
    *		var c = new YAHOO.widget.Calendar("calContainer", configOptions);
    *	</xmp>
    * or:
    *   <xmp>
    *       var containerDiv = YAHOO.util.Dom.get("calContainer");
    *		var c = new YAHOO.widget.Calendar(containerDiv, configOptions);
    *	</xmp>
    * </p>
    * <p>
    * If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
    * For example if an ID is not provided, and the container's ID is "calContainer", the Calendar's ID will be set to "calContainer_t".
    * </p>
    * 
    * @namespace YAHOO.widget
    * @class Calendar
    * @constructor
    * @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
    * @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
    * @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
    */
    function Calendar(id, containerId, config) {
        this.init.apply(this, arguments);
    }

    /**
    * The path to be used for images loaded for the Calendar
    * @property YAHOO.widget.Calendar.IMG_ROOT
    * @static
    * @deprecated	You can now customize images by overriding the calclose, calnavleft and calnavright default CSS classes for the close icon, left arrow and right arrow respectively
    * @type String
    */
    Calendar.IMG_ROOT = null;

    /**
    * Type constant used for renderers to represent an individual date (M/D/Y)
    * @property YAHOO.widget.Calendar.DATE
    * @static
    * @final
    * @type String
    */
    Calendar.DATE = "D";

    /**
    * Type constant used for renderers to represent an individual date across any year (M/D)
    * @property YAHOO.widget.Calendar.MONTH_DAY
    * @static
    * @final
    * @type String
    */
    Calendar.MONTH_DAY = "MD";

    /**
    * Type constant used for renderers to represent a weekday
    * @property YAHOO.widget.Calendar.WEEKDAY
    * @static
    * @final
    * @type String
    */
    Calendar.WEEKDAY = "WD";

    /**
    * Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y)
    * @property YAHOO.widget.Calendar.RANGE
    * @static
    * @final
    * @type String
    */
    Calendar.RANGE = "R";

    /**
    * Type constant used for renderers to represent a month across any year
    * @property YAHOO.widget.Calendar.MONTH
    * @static
    * @final
    * @type String
    */
    Calendar.MONTH = "M";

    /**
    * Constant that represents the total number of date cells that are displayed in a given month
    * @property YAHOO.widget.Calendar.DISPLAY_DAYS
    * @static
    * @final
    * @type Number
    */
    Calendar.DISPLAY_DAYS = 42;

    /**
    * Constant used for halting the execution of the remainder of the render stack
    * @property YAHOO.widget.Calendar.STOP_RENDER
    * @static
    * @final
    * @type String
    */
    Calendar.STOP_RENDER = "S";

    /**
    * Constant used to represent short date field string formats (e.g. Tu or Feb)
    * @property YAHOO.widget.Calendar.SHORT
    * @static
    * @final
    * @type String
    */
    Calendar.SHORT = "short";

    /**
    * Constant used to represent long date field string formats (e.g. Monday or February)
    * @property YAHOO.widget.Calendar.LONG
    * @static
    * @final
    * @type String
    */
    Calendar.LONG = "long";

    /**
    * Constant used to represent medium date field string formats (e.g. Mon)
    * @property YAHOO.widget.Calendar.MEDIUM
    * @static
    * @final
    * @type String
    */
    Calendar.MEDIUM = "medium";

    /**
    * Constant used to represent single character date field string formats (e.g. M, T, W)
    * @property YAHOO.widget.Calendar.ONE_CHAR
    * @static
    * @final
    * @type String
    */
    Calendar.ONE_CHAR = "1char";

    /**
    * The set of default Config property keys and values for the Calendar
    * @property YAHOO.widget.Calendar._DEFAULT_CONFIG
    * @final
    * @static
    * @private
    * @type Object
    */
    Calendar._DEFAULT_CONFIG = {
        // Default values for pagedate and selected are not class level constants - they are set during instance creation 
        PAGEDATE: { key: "pagedate", value: null },
        SELECTED: { key: "selected", value: null },
        TITLE: { key: "title", value: "" },
        CLOSE: { key: "close", value: false },
        IFRAME: { key: "iframe", value: (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) ? true : false },
        MINDATE: { key: "mindate", value: null },
        MAXDATE: { key: "maxdate", value: null },
        MULTI_SELECT: { key: "multi_select", value: false },
        START_WEEKDAY: { key: "start_weekday", value: 0 },
        SHOW_WEEKDAYS: { key: "show_weekdays", value: true },
        SHOW_WEEK_HEADER: { key: "show_week_header", value: false },
        SHOW_WEEK_FOOTER: { key: "show_week_footer", value: false },
        HIDE_BLANK_WEEKS: { key: "hide_blank_weeks", value: false },
        NAV_ARROW_LEFT: { key: "nav_arrow_left", value: null },
        NAV_ARROW_RIGHT: { key: "nav_arrow_right", value: null },
        MONTHS_SHORT: { key: "months_short", value: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] },
        MONTHS_LONG: { key: "months_long", value: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] },
        WEEKDAYS_1CHAR: { key: "weekdays_1char", value: ["S", "M", "T", "W", "T", "F", "S"] },
        WEEKDAYS_SHORT: { key: "weekdays_short", value: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] },
        WEEKDAYS_MEDIUM: { key: "weekdays_medium", value: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] },
        WEEKDAYS_LONG: { key: "weekdays_long", value: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] },
        LOCALE_MONTHS: { key: "locale_months", value: "long" },
        LOCALE_WEEKDAYS: { key: "locale_weekdays", value: "short" },
        DATE_DELIMITER: { key: "date_delimiter", value: "," },
        DATE_FIELD_DELIMITER: { key: "date_field_delimiter", value: "/" },
        DATE_RANGE_DELIMITER: { key: "date_range_delimiter", value: "-" },
        MY_MONTH_POSITION: { key: "my_month_position", value: 1 },
        MY_YEAR_POSITION: { key: "my_year_position", value: 2 },
        MD_MONTH_POSITION: { key: "md_month_position", value: 1 },
        MD_DAY_POSITION: { key: "md_day_position", value: 2 },
        MDY_MONTH_POSITION: { key: "mdy_month_position", value: 1 },
        MDY_DAY_POSITION: { key: "mdy_day_position", value: 2 },
        MDY_YEAR_POSITION: { key: "mdy_year_position", value: 3 },
        MY_LABEL_MONTH_POSITION: { key: "my_label_month_position", value: 1 },
        MY_LABEL_YEAR_POSITION: { key: "my_label_year_position", value: 2 },
        MY_LABEL_MONTH_SUFFIX: { key: "my_label_month_suffix", value: " " },
        MY_LABEL_YEAR_SUFFIX: { key: "my_label_year_suffix", value: "" },
        NAV: { key: "navigator", value: null },
        STRINGS: {
            key: "strings",
            value: {
                previousMonth: "Previous Month",
                nextMonth: "Next Month",
                close: "Close"
            },
            supercedes: ["close", "title"]
        }
    };

    var DEF_CFG = Calendar._DEFAULT_CONFIG;

    /**
    * The set of Custom Event types supported by the Calendar
    * @property YAHOO.widget.Calendar._EVENT_TYPES
    * @final
    * @static
    * @private
    * @type Object
    */
    Calendar._EVENT_TYPES = {
        BEFORE_SELECT: "beforeSelect",
        SELECT: "select",
        BEFORE_DESELECT: "beforeDeselect",
        DESELECT: "deselect",
        CHANGE_PAGE: "changePage",
        BEFORE_RENDER: "beforeRender",
        RENDER: "render",
        BEFORE_DESTROY: "beforeDestroy",
        DESTROY: "destroy",
        RESET: "reset",
        CLEAR: "clear",
        BEFORE_HIDE: "beforeHide",
        HIDE: "hide",
        BEFORE_SHOW: "beforeShow",
        SHOW: "show",
        BEFORE_HIDE_NAV: "beforeHideNav",
        HIDE_NAV: "hideNav",
        BEFORE_SHOW_NAV: "beforeShowNav",
        SHOW_NAV: "showNav",
        BEFORE_RENDER_NAV: "beforeRenderNav",
        RENDER_NAV: "renderNav"
    };

    /**
    * The set of default style constants for the Calendar
    * @property YAHOO.widget.Calendar._STYLES
    * @final
    * @static
    * @private
    * @type Object
    */
    Calendar._STYLES = {
        CSS_ROW_HEADER: "calrowhead",
        CSS_ROW_FOOTER: "calrowfoot",
        CSS_CELL: "calcell",
        CSS_CELL_SELECTOR: "selector",
        CSS_CELL_SELECTED: "selected",
        CSS_CELL_SELECTABLE: "selectable",
        CSS_CELL_RESTRICTED: "restricted",
        CSS_CELL_TODAY: "today",
        CSS_CELL_OOM: "oom",
        CSS_CELL_OOB: "previous",
        CSS_HEADER: "calheader",
        CSS_HEADER_TEXT: "calhead",
        CSS_BODY: "calbody",
        CSS_WEEKDAY_CELL: "calweekdaycell",
        CSS_WEEKDAY_ROW: "calweekdayrow",
        CSS_FOOTER: "calfoot",
        CSS_CALENDAR: "yui-calendar",
        CSS_SINGLE: "single",
        CSS_CONTAINER: "yui-calcontainer",
        CSS_NAV_LEFT: "calnavleft",
        CSS_NAV_RIGHT: "calnavright",
        CSS_NAV: "calnav",
        CSS_CLOSE: "calclose",
        CSS_CELL_TOP: "calcelltop",
        CSS_CELL_LEFT: "calcellleft",
        CSS_CELL_RIGHT: "calcellright",
        CSS_CELL_BOTTOM: "calcellbottom",
        CSS_CELL_HOVER: "calcellhover",
        CSS_CELL_HIGHLIGHT1: "highlight1",
        CSS_CELL_HIGHLIGHT2: "highlight2",
        CSS_CELL_HIGHLIGHT3: "highlight3",
        CSS_CELL_HIGHLIGHT4: "highlight4"
    };

    Calendar.prototype = {

        /**
        * The configuration object used to set up the calendars various locale and style options.
        * @property Config
        * @private
        * @deprecated Configuration properties should be set by calling Calendar.cfg.setProperty.
        * @type Object
        */
        Config: null,

        /**
        * The parent CalendarGroup, only to be set explicitly by the parent group
        * @property parent
        * @type CalendarGroup
        */
        parent: null,

        /**
        * The index of this item in the parent group
        * @property index
        * @type Number
        */
        index: -1,

        /**
        * The collection of calendar table cells
        * @property cells
        * @type HTMLTableCellElement[]
        */
        cells: null,

        /**
        * The collection of calendar cell dates that is parallel to the cells collection. The array contains dates field arrays in the format of [YYYY, M, D].
        * @property cellDates
        * @type Array[](Number[])
        */
        cellDates: null,

        /**
        * The id that uniquely identifies this Calendar.
        * @property id
        * @type String
        */
        id: null,

        /**
        * The unique id associated with the Calendar's container
        * @property containerId
        * @type String
        */
        containerId: null,

        /**
        * The DOM element reference that points to this calendar's container element. The calendar will be inserted into this element when the shell is rendered.
        * @property oDomContainer
        * @type HTMLElement
        */
        oDomContainer: null,

        /**
        * A Date object representing today's date.
        * @property today
        * @type Date
        */
        today: null,

        /**
        * The list of render functions, along with required parameters, used to render cells. 
        * @property renderStack
        * @type Array[]
        */
        renderStack: null,

        /**
        * A copy of the initial render functions created before rendering.
        * @property _renderStack
        * @private
        * @type Array
        */
        _renderStack: null,

        /**
        * A reference to the CalendarNavigator instance created for this Calendar.
        * Will be null if the "navigator" configuration property has not been set
        * @property oNavigator
        * @type CalendarNavigator
        */
        oNavigator: null,

        /**
        * The private list of initially selected dates.
        * @property _selectedDates
        * @private
        * @type Array
        */
        _selectedDates: null,

        /**
        * A map of DOM event handlers to attach to cells associated with specific CSS class names
        * @property domEventMap
        * @type Object
        */
        domEventMap: null,

        /**
        * Protected helper used to parse Calendar constructor/init arguments.
        *
        * As of 2.4.0, Calendar supports a simpler constructor 
        * signature. This method reconciles arguments
        * received in the pre 2.4.0 and 2.4.0 formats.
        * 
        * @protected
        * @method _parseArgs
        * @param {Array} Function "arguments" array
        * @return {Object} Object with id, container, config properties containing
        * the reconciled argument values.
        **/
        _parseArgs: function(args) {
            /*
            2.4.0 Constructors signatures

		   new Calendar(String)
            new Calendar(HTMLElement)
            new Calendar(String, ConfigObject)
            new Calendar(HTMLElement, ConfigObject)

		   Pre 2.4.0 Constructor signatures

		   new Calendar(String, String)
            new Calendar(String, HTMLElement)
            new Calendar(String, String, ConfigObject)
            new Calendar(String, HTMLElement, ConfigObject)
            */
            var nArgs = { id: null, container: null, config: null };

            if (args && args.length && args.length > 0) {
                switch (args.length) {
                    case 1:
                        nArgs.id = null;
                        nArgs.container = args[0];
                        nArgs.config = null;
                        break;
                    case 2:
                        if (Lang.isObject(args[1]) && !args[1].tagName && !(args[1] instanceof String)) {
                            nArgs.id = null;
                            nArgs.container = args[0];
                            nArgs.config = args[1];
                        } else {
                            nArgs.id = args[0];
                            nArgs.container = args[1];
                            nArgs.config = null;
                        }
                        break;
                    default: // 3+
                        nArgs.id = args[0];
                        nArgs.container = args[1];
                        nArgs.config = args[2];
                        break;
                }
            } else {
            }
            return nArgs;
        },

        /**
        * Initializes the Calendar widget.
        * @method init
        *
        * @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
        * @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
        * @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
        */
        init: function(id, container, config) {
            // Normalize 2.4.0, pre 2.4.0 args
            var nArgs = this._parseArgs(arguments);

            id = nArgs.id;
            container = nArgs.container;
            config = nArgs.config;

            this.oDomContainer = Dom.get(container);

            if (!this.oDomContainer.id) {
                this.oDomContainer.id = Dom.generateId();
            }
            if (!id) {
                id = this.oDomContainer.id + "_t";
            }

            this.id = id;
            this.containerId = this.oDomContainer.id;

            this.initEvents();

            this.today = new Date();
            DateMath.clearTime(this.today);

            /**
            * The Config object used to hold the configuration variables for the Calendar
            * @property cfg
            * @type YAHOO.util.Config
            */
            this.cfg = new YAHOO.util.Config(this);

            /**
            * The local object which contains the Calendar's options
            * @property Options
            * @type Object
            */
            this.Options = {};

            /**
            * The local object which contains the Calendar's locale settings
            * @property Locale
            * @type Object
            */
            this.Locale = {};

            this.initStyles();

            Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER);
            Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE);

            this.cellDates = [];
            this.cells = [];
            this.renderStack = [];
            this._renderStack = [];

            this.setupConfig();

            if (config) {
                this.cfg.applyConfig(config, true);
            }

            this.cfg.fireQueue();
        },

        /**
        * Default Config listener for the iframe property. If the iframe config property is set to true, 
        * renders the built-in IFRAME shim if the container is relatively or absolutely positioned.
        * 
        * @method configIframe
        */
        configIframe: function(type, args, obj) {
            var useIframe = args[0];

            if (!this.parent) {
                if (Dom.inDocument(this.oDomContainer)) {
                    if (useIframe) {
                        var pos = Dom.getStyle(this.oDomContainer, "position");

                        if (pos == "absolute" || pos == "relative") {

                            if (!Dom.inDocument(this.iframe)) {
                                this.iframe = document.createElement("iframe");
                                this.iframe.src = "javascript:false;";

                                Dom.setStyle(this.iframe, "opacity", "0");

                                if (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) {
                                    Dom.addClass(this.iframe, "fixedsize");
                                }

                                this.oDomContainer.insertBefore(this.iframe, this.oDomContainer.firstChild);
                            }
                        }
                    } else {
                        if (this.iframe) {
                            if (this.iframe.parentNode) {
                                this.iframe.parentNode.removeChild(this.iframe);
                            }
                            this.iframe = null;
                        }
                    }
                }
            }
        },

        /**
        * Default handler for the "title" property
        * @method configTitle
        */
        configTitle: function(type, args, obj) {
            var title = args[0];

            // "" disables title bar
            if (title) {
                this.createTitleBar(title);
            } else {
                var close = this.cfg.getProperty(DEF_CFG.CLOSE.key);
                if (!close) {
                    this.removeTitleBar();
                } else {
                    this.createTitleBar("&#160;");
                }
            }
        },

        /**
        * Default handler for the "close" property
        * @method configClose
        */
        configClose: function(type, args, obj) {
            var close = args[0],
			title = this.cfg.getProperty(DEF_CFG.TITLE.key);

            if (close) {
                if (!title) {
                    this.createTitleBar("&#160;");
                }
                this.createCloseButton();
            } else {
                this.removeCloseButton();
                if (!title) {
                    this.removeTitleBar();
                }
            }
        },

        /**
        * Initializes Calendar's built-in CustomEvents
        * @method initEvents
        */
        initEvents: function() {

            var defEvents = Calendar._EVENT_TYPES,
			CE = YAHOO.util.CustomEvent,
			cal = this; // To help with minification

            /**
            * Fired before a selection is made
            * @event beforeSelectEvent
            */
            cal.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT);

            /**
            * Fired when a selection is made
            * @event selectEvent
            * @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
            */
            cal.selectEvent = new CE(defEvents.SELECT);

            /**
            * Fired before a selection is made
            * @event beforeDeselectEvent
            */
            cal.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT);

            /**
            * Fired when a selection is made
            * @event deselectEvent
            * @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
            */
            cal.deselectEvent = new CE(defEvents.DESELECT);

            /**
            * Fired when the Calendar page is changed
            * @event changePageEvent
            */
            cal.changePageEvent = new CE(defEvents.CHANGE_PAGE);

            /**
            * Fired before the Calendar is rendered
            * @event beforeRenderEvent
            */
            cal.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER);

            /**
            * Fired when the Calendar is rendered
            * @event renderEvent
            */
            cal.renderEvent = new CE(defEvents.RENDER);

            /**
            * Fired just before the Calendar is to be destroyed
            * @event beforeDestroyEvent
            */
            cal.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY);

            /**
            * Fired after the Calendar is destroyed. This event should be used
            * for notification only. When this event is fired, important Calendar instance
            * properties, dom references and event listeners have already been 
            * removed/dereferenced, and hence the Calendar instance is not in a usable 
            * state.
            *
            * @event destroyEvent
            */
            cal.destroyEvent = new CE(defEvents.DESTROY);

            /**
            * Fired when the Calendar is reset
            * @event resetEvent
            */
            cal.resetEvent = new CE(defEvents.RESET);

            /**
            * Fired when the Calendar is cleared
            * @event clearEvent
            */
            cal.clearEvent = new CE(defEvents.CLEAR);

            /**
            * Fired just before the Calendar is to be shown
            * @event beforeShowEvent
            */
            cal.beforeShowEvent = new CE(defEvents.BEFORE_SHOW);

            /**
            * Fired after the Calendar is shown
            * @event showEvent
            */
            cal.showEvent = new CE(defEvents.SHOW);

            /**
            * Fired just before the Calendar is to be hidden
            * @event beforeHideEvent
            */
            cal.beforeHideEvent = new CE(defEvents.BEFORE_HIDE);

            /**
            * Fired after the Calendar is hidden
            * @event hideEvent
            */
            cal.hideEvent = new CE(defEvents.HIDE);

            /**
            * Fired just before the CalendarNavigator is to be shown
            * @event beforeShowNavEvent
            */
            cal.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV);

            /**
            * Fired after the CalendarNavigator is shown
            * @event showNavEvent
            */
            cal.showNavEvent = new CE(defEvents.SHOW_NAV);

            /**
            * Fired just before the CalendarNavigator is to be hidden
            * @event beforeHideNavEvent
            */
            cal.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV);

            /**
            * Fired after the CalendarNavigator is hidden
            * @event hideNavEvent
            */
            cal.hideNavEvent = new CE(defEvents.HIDE_NAV);

            /**
            * Fired just before the CalendarNavigator is to be rendered
            * @event beforeRenderNavEvent
            */
            cal.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV);

            /**
            * Fired after the CalendarNavigator is rendered
            * @event renderNavEvent
            */
            cal.renderNavEvent = new CE(defEvents.RENDER_NAV);

            cal.beforeSelectEvent.subscribe(cal.onBeforeSelect, this, true);
            cal.selectEvent.subscribe(cal.onSelect, this, true);
            cal.beforeDeselectEvent.subscribe(cal.onBeforeDeselect, this, true);
            cal.deselectEvent.subscribe(cal.onDeselect, this, true);
            cal.changePageEvent.subscribe(cal.onChangePage, this, true);
            cal.renderEvent.subscribe(cal.onRender, this, true);
            cal.resetEvent.subscribe(cal.onReset, this, true);
            cal.clearEvent.subscribe(cal.onClear, this, true);
        },

        /**
        * The default event handler for clicks on the "Previous Month" navigation UI
        *
        * @method doPreviousMonthNav
        * @param {DOMEvent} e	The DOM event
        * @param {Calendar} cal	A reference to the calendar
        */
        doPreviousMonthNav: function(e, cal) {
            Event.preventDefault(e);
            // previousMonth invoked in a timeout, to allow
            // event to bubble up, with correct target. Calling
            // previousMonth, will call render which will remove 
            // HTML which generated the event, resulting in an 
            // invalid event target in certain browsers.
            setTimeout(function() {
                cal.previousMonth();
                var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_LEFT, "a", cal.oDomContainer);
                if (navs && navs[0]) {
                    try {
                        navs[0].focus();
                    } catch (e) {
                        // ignore
                    }
                }
            }, 0);
        },

        /**
        * The default event handler for clicks on the "Next Month" navigation UI
        *
        * @method doNextMonthNav
        * @param {DOMEvent} e	The DOM event
        * @param {Calendar} cal	A reference to the calendar
        */
        doNextMonthNav: function(e, cal) {
            Event.preventDefault(e);
            setTimeout(function() {
                cal.nextMonth();
                var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_RIGHT, "a", cal.oDomContainer);
                if (navs && navs[0]) {
                    try {
                        navs[0].focus();
                    } catch (e) {
                        // ignore
                    }
                }
            }, 0);
        },

        /**
        * The default event handler for date cell selection. Currently attached to 
        * the Calendar's bounding box, referenced by it's <a href="#property_oDomContainer">oDomContainer</a> property.
        *
        * @method doSelectCell
        * @param {DOMEvent} e	The DOM event
        * @param {Calendar} cal	A reference to the calendar
        */
        doSelectCell: function(e, cal) {
            var cell, d, date, index;

            var target = Event.getTarget(e),
			tagName = target.tagName.toLowerCase(),
			defSelector = false;

            while (tagName != "td" && !Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {

                if (!defSelector && tagName == "a" && Dom.hasClass(target, cal.Style.CSS_CELL_SELECTOR)) {
                    defSelector = true;
                }

                target = target.parentNode;
                tagName = target.tagName.toLowerCase();

                if (target == this.oDomContainer || tagName == "html") {
                    return;
                }
            }

            if (defSelector) {
                // Stop link href navigation for default renderer
                Event.preventDefault(e);
            }

            cell = target;

            if (Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) {
                index = cal.getIndexFromId(cell.id);
                if (index > -1) {
                    d = cal.cellDates[index];
                    if (d) {
                        date = DateMath.getDate(d[0], d[1] - 1, d[2]);

                        var link;

                        if (cal.Options.MULTI_SELECT) {
                            link = cell.getElementsByTagName("a")[0];
                            if (link) {
                                link.blur();
                            }

                            var cellDate = cal.cellDates[index];
                            var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate);

                            if (cellDateIndex > -1) {
                                cal.deselectCell(index);
                            } else {
                                cal.selectCell(index);
                            }

                        } else {
                            link = cell.getElementsByTagName("a")[0];
                            if (link) {
                                link.blur();
                            }
                            cal.selectCell(index);
                        }
                    }
                }
            }
        },

        /**
        * The event that is executed when the user hovers over a cell
        * @method doCellMouseOver
        * @param {DOMEvent} e	The event
        * @param {Calendar} cal	A reference to the calendar passed by the Event utility
        */
        doCellMouseOver: function(e, cal) {
            var target;
            if (e) {
                target = Event.getTarget(e);
            } else {
                target = this;
            }

            while (target.tagName && target.tagName.toLowerCase() != "td") {
                target = target.parentNode;
                if (!target.tagName || target.tagName.toLowerCase() == "html") {
                    return;
                }
            }

            if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
                Dom.addClass(target, cal.Style.CSS_CELL_HOVER);
            }
        },

        /**
        * The event that is executed when the user moves the mouse out of a cell
        * @method doCellMouseOut
        * @param {DOMEvent} e	The event
        * @param {Calendar} cal	A reference to the calendar passed by the Event utility
        */
        doCellMouseOut: function(e, cal) {
            var target;
            if (e) {
                target = Event.getTarget(e);
            } else {
                target = this;
            }

            while (target.tagName && target.tagName.toLowerCase() != "td") {
                target = target.parentNode;
                if (!target.tagName || target.tagName.toLowerCase() == "html") {
                    return;
                }
            }

            if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
                Dom.removeClass(target, cal.Style.CSS_CELL_HOVER);
            }
        },

        setupConfig: function() {
            var cfg = this.cfg;

            /**
            * The month/year representing the current visible Calendar date (mm/yyyy)
            * @config pagedate
            * @type String | Date
            * @default today's date
            */
            cfg.addProperty(DEF_CFG.PAGEDATE.key, { value: new Date(), handler: this.configPageDate });

            /**
            * The date or range of dates representing the current Calendar selection
            * @config selected
            * @type String
            * @default []
            */
            cfg.addProperty(DEF_CFG.SELECTED.key, { value: [], handler: this.configSelected });

            /**
            * The title to display above the Calendar's month header
            * @config title
            * @type String
            * @default ""
            */
            cfg.addProperty(DEF_CFG.TITLE.key, { value: DEF_CFG.TITLE.value, handler: this.configTitle });

            /**
            * Whether or not a close button should be displayed for this Calendar
            * @config close
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.CLOSE.key, { value: DEF_CFG.CLOSE.value, handler: this.configClose });

            /**
            * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
            * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be 
            * enabled if required.
            * 
            * @config iframe
            * @type Boolean
            * @default true for IE6 and below, false for all other browsers
            */
            cfg.addProperty(DEF_CFG.IFRAME.key, { value: DEF_CFG.IFRAME.value, handler: this.configIframe, validator: cfg.checkBoolean });

            /**
            * The minimum selectable date in the current Calendar (mm/dd/yyyy)
            * @config mindate
            * @type String | Date
            * @default null
            */
            cfg.addProperty(DEF_CFG.MINDATE.key, { value: DEF_CFG.MINDATE.value, handler: this.configMinDate });

            /**
            * The maximum selectable date in the current Calendar (mm/dd/yyyy)
            * @config maxdate
            * @type String | Date
            * @default null
            */
            cfg.addProperty(DEF_CFG.MAXDATE.key, { value: DEF_CFG.MAXDATE.value, handler: this.configMaxDate });


            // Options properties

            /**
            * True if the Calendar should allow multiple selections. False by default.
            * @config MULTI_SELECT
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.MULTI_SELECT.key, { value: DEF_CFG.MULTI_SELECT.value, handler: this.configOptions, validator: cfg.checkBoolean });

            /**
            * The weekday the week begins on. Default is 0 (Sunday = 0, Monday = 1 ... Saturday = 6).
            * @config START_WEEKDAY
            * @type number
            * @default 0
            */
            cfg.addProperty(DEF_CFG.START_WEEKDAY.key, { value: DEF_CFG.START_WEEKDAY.value, handler: this.configOptions, validator: cfg.checkNumber });

            /**
            * True if the Calendar should show weekday labels. True by default.
            * @config SHOW_WEEKDAYS
            * @type Boolean
            * @default true
            */
            cfg.addProperty(DEF_CFG.SHOW_WEEKDAYS.key, { value: DEF_CFG.SHOW_WEEKDAYS.value, handler: this.configOptions, validator: cfg.checkBoolean });

            /**
            * True if the Calendar should show week row headers. False by default.
            * @config SHOW_WEEK_HEADER
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.SHOW_WEEK_HEADER.key, { value: DEF_CFG.SHOW_WEEK_HEADER.value, handler: this.configOptions, validator: cfg.checkBoolean });

            /**
            * True if the Calendar should show week row footers. False by default.
            * @config SHOW_WEEK_FOOTER
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.SHOW_WEEK_FOOTER.key, { value: DEF_CFG.SHOW_WEEK_FOOTER.value, handler: this.configOptions, validator: cfg.checkBoolean });

            /**
            * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
            * @config HIDE_BLANK_WEEKS
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.HIDE_BLANK_WEEKS.key, { value: DEF_CFG.HIDE_BLANK_WEEKS.value, handler: this.configOptions, validator: cfg.checkBoolean });

            /**
            * The image that should be used for the left navigation arrow.
            * @config NAV_ARROW_LEFT
            * @type String
            * @deprecated	You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"  
            * @default null
            */
            cfg.addProperty(DEF_CFG.NAV_ARROW_LEFT.key, { value: DEF_CFG.NAV_ARROW_LEFT.value, handler: this.configOptions });

            /**
            * The image that should be used for the right navigation arrow.
            * @config NAV_ARROW_RIGHT
            * @type String
            * @deprecated	You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
            * @default null
            */
            cfg.addProperty(DEF_CFG.NAV_ARROW_RIGHT.key, { value: DEF_CFG.NAV_ARROW_RIGHT.value, handler: this.configOptions });

            // Locale properties

            /**
            * The short month labels for the current locale.
            * @config MONTHS_SHORT
            * @type String[]
            * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
            */
            cfg.addProperty(DEF_CFG.MONTHS_SHORT.key, { value: DEF_CFG.MONTHS_SHORT.value, handler: this.configLocale });

            /**
            * The long month labels for the current locale.
            * @config MONTHS_LONG
            * @type String[]
            * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
            */
            cfg.addProperty(DEF_CFG.MONTHS_LONG.key, { value: DEF_CFG.MONTHS_LONG.value, handler: this.configLocale });

            /**
            * The 1-character weekday labels for the current locale.
            * @config WEEKDAYS_1CHAR
            * @type String[]
            * @default ["S", "M", "T", "W", "T", "F", "S"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_1CHAR.key, { value: DEF_CFG.WEEKDAYS_1CHAR.value, handler: this.configLocale });

            /**
            * The short weekday labels for the current locale.
            * @config WEEKDAYS_SHORT
            * @type String[]
            * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_SHORT.key, { value: DEF_CFG.WEEKDAYS_SHORT.value, handler: this.configLocale });

            /**
            * The medium weekday labels for the current locale.
            * @config WEEKDAYS_MEDIUM
            * @type String[]
            * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_MEDIUM.key, { value: DEF_CFG.WEEKDAYS_MEDIUM.value, handler: this.configLocale });

            /**
            * The long weekday labels for the current locale.
            * @config WEEKDAYS_LONG
            * @type String[]
            * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_LONG.key, { value: DEF_CFG.WEEKDAYS_LONG.value, handler: this.configLocale });

            /**
            * Refreshes the locale values used to build the Calendar.
            * @method refreshLocale
            * @private
            */
            var refreshLocale = function() {
                cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key);
                cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key);
            };

            cfg.subscribeToConfigEvent(DEF_CFG.START_WEEKDAY.key, refreshLocale, this, true);
            cfg.subscribeToConfigEvent(DEF_CFG.MONTHS_SHORT.key, refreshLocale, this, true);
            cfg.subscribeToConfigEvent(DEF_CFG.MONTHS_LONG.key, refreshLocale, this, true);
            cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_1CHAR.key, refreshLocale, this, true);
            cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_SHORT.key, refreshLocale, this, true);
            cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_MEDIUM.key, refreshLocale, this, true);
            cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_LONG.key, refreshLocale, this, true);

            /**
            * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
            * @config LOCALE_MONTHS
            * @type String
            * @default "long"
            */
            cfg.addProperty(DEF_CFG.LOCALE_MONTHS.key, { value: DEF_CFG.LOCALE_MONTHS.value, handler: this.configLocaleValues });

            /**
            * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
            * @config LOCALE_WEEKDAYS
            * @type String
            * @default "short"
            */
            cfg.addProperty(DEF_CFG.LOCALE_WEEKDAYS.key, { value: DEF_CFG.LOCALE_WEEKDAYS.value, handler: this.configLocaleValues });

            /**
            * The value used to delimit individual dates in a date string passed to various Calendar functions.
            * @config DATE_DELIMITER
            * @type String
            * @default ","
            */
            cfg.addProperty(DEF_CFG.DATE_DELIMITER.key, { value: DEF_CFG.DATE_DELIMITER.value, handler: this.configLocale });

            /**
            * The value used to delimit date fields in a date string passed to various Calendar functions.
            * @config DATE_FIELD_DELIMITER
            * @type String
            * @default "/"
            */
            cfg.addProperty(DEF_CFG.DATE_FIELD_DELIMITER.key, { value: DEF_CFG.DATE_FIELD_DELIMITER.value, handler: this.configLocale });

            /**
            * The value used to delimit date ranges in a date string passed to various Calendar functions.
            * @config DATE_RANGE_DELIMITER
            * @type String
            * @default "-"
            */
            cfg.addProperty(DEF_CFG.DATE_RANGE_DELIMITER.key, { value: DEF_CFG.DATE_RANGE_DELIMITER.value, handler: this.configLocale });

            /**
            * The position of the month in a month/year date string
            * @config MY_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MY_MONTH_POSITION.key, { value: DEF_CFG.MY_MONTH_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the year in a month/year date string
            * @config MY_YEAR_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MY_YEAR_POSITION.key, { value: DEF_CFG.MY_YEAR_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the month in a month/day date string
            * @config MD_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MD_MONTH_POSITION.key, { value: DEF_CFG.MD_MONTH_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the day in a month/year date string
            * @config MD_DAY_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MD_DAY_POSITION.key, { value: DEF_CFG.MD_DAY_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the month in a month/day/year date string
            * @config MDY_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MDY_MONTH_POSITION.key, { value: DEF_CFG.MDY_MONTH_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the day in a month/day/year date string
            * @config MDY_DAY_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MDY_DAY_POSITION.key, { value: DEF_CFG.MDY_DAY_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the year in a month/day/year date string
            * @config MDY_YEAR_POSITION
            * @type Number
            * @default 3
            */
            cfg.addProperty(DEF_CFG.MDY_YEAR_POSITION.key, { value: DEF_CFG.MDY_YEAR_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the month in the month year label string used as the Calendar header
            * @config MY_LABEL_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_POSITION.key, { value: DEF_CFG.MY_LABEL_MONTH_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The position of the year in the month year label string used as the Calendar header
            * @config MY_LABEL_YEAR_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_POSITION.key, { value: DEF_CFG.MY_LABEL_YEAR_POSITION.value, handler: this.configLocale, validator: cfg.checkNumber });

            /**
            * The suffix used after the month when rendering the Calendar header
            * @config MY_LABEL_MONTH_SUFFIX
            * @type String
            * @default " "
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_SUFFIX.key, { value: DEF_CFG.MY_LABEL_MONTH_SUFFIX.value, handler: this.configLocale });

            /**
            * The suffix used after the year when rendering the Calendar header
            * @config MY_LABEL_YEAR_SUFFIX
            * @type String
            * @default ""
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_SUFFIX.key, { value: DEF_CFG.MY_LABEL_YEAR_SUFFIX.value, handler: this.configLocale });

            /**
            * Configuration for the Month/Year CalendarNavigator UI which allows the user to jump directly to a 
            * specific Month/Year without having to scroll sequentially through months.
            * <p>
            * Setting this property to null (default value) or false, will disable the CalendarNavigator UI.
            * </p>
            * <p>
            * Setting this property to true will enable the CalendarNavigatior UI with the default CalendarNavigator configuration values.
            * </p>
            * <p>
            * This property can also be set to an object literal containing configuration properties for the CalendarNavigator UI.
            * The configuration object expects the the following case-sensitive properties, with the "strings" property being a nested object.
            * Any properties which are not provided will use the default values (defined in the CalendarNavigator class).
            * </p>
            * <dl>
            * <dt>strings</dt>
            * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI
            *     <dl>
            *         <dt>month</dt><dd><em>String</em> : The string to use for the month label. Defaults to "Month".</dd>
            *         <dt>year</dt><dd><em>String</em> : The string to use for the year label. Defaults to "Year".</dd>
            *         <dt>submit</dt><dd><em>String</em> : The string to use for the submit button label. Defaults to "Okay".</dd>
            *         <dt>cancel</dt><dd><em>String</em> : The string to use for the cancel button label. Defaults to "Cancel".</dd>
            *         <dt>invalidYear</dt><dd><em>String</em> : The string to use for invalid year values. Defaults to "Year needs to be a number".</dd>
            *     </dl>
            * </dd>
            * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
            * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
            * </dl>
            * <p>E.g.</p>
            * <pre>
            * var navConfig = {
            *	  strings: {
            *		  month:"Calendar Month",
            *		  year:"Calendar Year",
            *		  submit: "Submit",
            *		  cancel: "Cancel",
            *		  invalidYear: "Please enter a valid year"
            *	  },
            *	  monthFormat: YAHOO.widget.Calendar.SHORT,
            *	  initialFocus: "month"
            * }
            * </pre>
            * @config navigator
            * @type {Object|Boolean}
            * @default null
            */
            cfg.addProperty(DEF_CFG.NAV.key, { value: DEF_CFG.NAV.value, handler: this.configNavigator });

            /**
            * The map of UI strings which the Calendar UI uses.
            *
            * @config strings
            * @type {Object}
            * @default An object with the properties shown below:
            *     <dl>
            *         <dt>previousMonth</dt><dd><em>String</em> : The string to use for the "Previous Month" navigation UI. Defaults to "Previous Month".</dd>
            *         <dt>nextMonth</dt><dd><em>String</em> : The string to use for the "Next Month" navigation UI. Defaults to "Next Month".</dd>
            *         <dt>close</dt><dd><em>String</em> : The string to use for the close button label. Defaults to "Close".</dd>
            *     </dl>
            */
            cfg.addProperty(DEF_CFG.STRINGS.key, {
                value: DEF_CFG.STRINGS.value,
                handler: this.configStrings,
                validator: function(val) {
                    return Lang.isObject(val);
                },
                supercedes: DEF_CFG.STRINGS.supercedes
            });
        },

        /**
        * The default handler for the "strings" property
        * @method configStrings
        */
        configStrings: function(type, args, obj) {
            var val = Lang.merge(DEF_CFG.STRINGS.value, args[0]);
            this.cfg.setProperty(DEF_CFG.STRINGS.key, val, true);
        },

        /**
        * The default handler for the "pagedate" property
        * @method configPageDate
        */
        configPageDate: function(type, args, obj) {
            this.cfg.setProperty(DEF_CFG.PAGEDATE.key, this._parsePageDate(args[0]), true);
        },

        /**
        * The default handler for the "mindate" property
        * @method configMinDate
        */
        configMinDate: function(type, args, obj) {
            var val = args[0];
            if (Lang.isString(val)) {
                val = this._parseDate(val);
                this.cfg.setProperty(DEF_CFG.MINDATE.key, DateMath.getDate(val[0], (val[1] - 1), val[2]));
            }
        },

        /**
        * The default handler for the "maxdate" property
        * @method configMaxDate
        */
        configMaxDate: function(type, args, obj) {
            var val = args[0];
            if (Lang.isString(val)) {
                val = this._parseDate(val);
                this.cfg.setProperty(DEF_CFG.MAXDATE.key, DateMath.getDate(val[0], (val[1] - 1), val[2]));
            }
        },

        /**
        * The default handler for the "selected" property
        * @method configSelected
        */
        configSelected: function(type, args, obj) {
            var selected = args[0],
			cfgSelected = DEF_CFG.SELECTED.key;

            if (selected) {
                if (Lang.isString(selected)) {
                    this.cfg.setProperty(cfgSelected, this._parseDates(selected), true);
                }
            }
            if (!this._selectedDates) {
                this._selectedDates = this.cfg.getProperty(cfgSelected);
            }
        },

        /**
        * The default handler for all configuration options properties
        * @method configOptions
        */
        configOptions: function(type, args, obj) {
            this.Options[type.toUpperCase()] = args[0];
        },

        /**
        * The default handler for all configuration locale properties
        * @method configLocale
        */
        configLocale: function(type, args, obj) {
            this.Locale[type.toUpperCase()] = args[0];

            this.cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key);
            this.cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key);
        },

        /**
        * The default handler for all configuration locale field length properties
        * @method configLocaleValues
        */
        configLocaleValues: function(type, args, obj) {

            type = type.toLowerCase();

            var val = args[0],
			cfg = this.cfg,
			Locale = this.Locale;

            switch (type) {
                case DEF_CFG.LOCALE_MONTHS.key:
                    switch (val) {
                        case Calendar.SHORT:
                            Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_SHORT.key).concat();
                            break;
                        case Calendar.LONG:
                            Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_LONG.key).concat();
                            break;
                    }
                    break;
                case DEF_CFG.LOCALE_WEEKDAYS.key:
                    switch (val) {
                        case Calendar.ONE_CHAR:
                            Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_1CHAR.key).concat();
                            break;
                        case Calendar.SHORT:
                            Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_SHORT.key).concat();
                            break;
                        case Calendar.MEDIUM:
                            Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_MEDIUM.key).concat();
                            break;
                        case Calendar.LONG:
                            Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_LONG.key).concat();
                            break;
                    }

                    var START_WEEKDAY = cfg.getProperty(DEF_CFG.START_WEEKDAY.key);

                    if (START_WEEKDAY > 0) {
                        for (var w = 0; w < START_WEEKDAY; ++w) {
                            Locale.LOCALE_WEEKDAYS.push(Locale.LOCALE_WEEKDAYS.shift());
                        }
                    }
                    break;
            }
        },

        /**
        * The default handler for the "navigator" property
        * @method configNavigator
        */
        configNavigator: function(type, args, obj) {
            var val = args[0];
            if (YAHOO.widget.CalendarNavigator && (val === true || Lang.isObject(val))) {
                if (!this.oNavigator) {
                    this.oNavigator = new YAHOO.widget.CalendarNavigator(this);
                    // Cleanup DOM Refs/Events before innerHTML is removed.
                    this.beforeRenderEvent.subscribe(function() {
                        if (!this.pages) {
                            this.oNavigator.erase();
                        }
                    }, this, true);
                }
            } else {
                if (this.oNavigator) {
                    this.oNavigator.destroy();
                    this.oNavigator = null;
                }
            }
        },

        /**
        * Defines the style constants for the Calendar
        * @method initStyles
        */
        initStyles: function() {

            var defStyle = Calendar._STYLES;

            this.Style = {
                /**
                * @property Style.CSS_ROW_HEADER
                */
                CSS_ROW_HEADER: defStyle.CSS_ROW_HEADER,
                /**
                * @property Style.CSS_ROW_FOOTER
                */
                CSS_ROW_FOOTER: defStyle.CSS_ROW_FOOTER,
                /**
                * @property Style.CSS_CELL
                */
                CSS_CELL: defStyle.CSS_CELL,
                /**
                * @property Style.CSS_CELL_SELECTOR
                */
                CSS_CELL_SELECTOR: defStyle.CSS_CELL_SELECTOR,
                /**
                * @property Style.CSS_CELL_SELECTED
                */
                CSS_CELL_SELECTED: defStyle.CSS_CELL_SELECTED,
                /**
                * @property Style.CSS_CELL_SELECTABLE
                */
                CSS_CELL_SELECTABLE: defStyle.CSS_CELL_SELECTABLE,
                /**
                * @property Style.CSS_CELL_RESTRICTED
                */
                CSS_CELL_RESTRICTED: defStyle.CSS_CELL_RESTRICTED,
                /**
                * @property Style.CSS_CELL_TODAY
                */
                CSS_CELL_TODAY: defStyle.CSS_CELL_TODAY,
                /**
                * @property Style.CSS_CELL_OOM
                */
                CSS_CELL_OOM: defStyle.CSS_CELL_OOM,
                /**
                * @property Style.CSS_CELL_OOB
                */
                CSS_CELL_OOB: defStyle.CSS_CELL_OOB,
                /**
                * @property Style.CSS_HEADER
                */
                CSS_HEADER: defStyle.CSS_HEADER,
                /**
                * @property Style.CSS_HEADER_TEXT
                */
                CSS_HEADER_TEXT: defStyle.CSS_HEADER_TEXT,
                /**
                * @property Style.CSS_BODY
                */
                CSS_BODY: defStyle.CSS_BODY,
                /**
                * @property Style.CSS_WEEKDAY_CELL
                */
                CSS_WEEKDAY_CELL: defStyle.CSS_WEEKDAY_CELL,
                /**
                * @property Style.CSS_WEEKDAY_ROW
                */
                CSS_WEEKDAY_ROW: defStyle.CSS_WEEKDAY_ROW,
                /**
                * @property Style.CSS_FOOTER
                */
                CSS_FOOTER: defStyle.CSS_FOOTER,
                /**
                * @property Style.CSS_CALENDAR
                */
                CSS_CALENDAR: defStyle.CSS_CALENDAR,
                /**
                * @property Style.CSS_SINGLE
                */
                CSS_SINGLE: defStyle.CSS_SINGLE,
                /**
                * @property Style.CSS_CONTAINER
                */
                CSS_CONTAINER: defStyle.CSS_CONTAINER,
                /**
                * @property Style.CSS_NAV_LEFT
                */
                CSS_NAV_LEFT: defStyle.CSS_NAV_LEFT,
                /**
                * @property Style.CSS_NAV_RIGHT
                */
                CSS_NAV_RIGHT: defStyle.CSS_NAV_RIGHT,
                /**
                * @property Style.CSS_NAV
                */
                CSS_NAV: defStyle.CSS_NAV,
                /**
                * @property Style.CSS_CLOSE
                */
                CSS_CLOSE: defStyle.CSS_CLOSE,
                /**
                * @property Style.CSS_CELL_TOP
                */
                CSS_CELL_TOP: defStyle.CSS_CELL_TOP,
                /**
                * @property Style.CSS_CELL_LEFT
                */
                CSS_CELL_LEFT: defStyle.CSS_CELL_LEFT,
                /**
                * @property Style.CSS_CELL_RIGHT
                */
                CSS_CELL_RIGHT: defStyle.CSS_CELL_RIGHT,
                /**
                * @property Style.CSS_CELL_BOTTOM
                */
                CSS_CELL_BOTTOM: defStyle.CSS_CELL_BOTTOM,
                /**
                * @property Style.CSS_CELL_HOVER
                */
                CSS_CELL_HOVER: defStyle.CSS_CELL_HOVER,
                /**
                * @property Style.CSS_CELL_HIGHLIGHT1
                */
                CSS_CELL_HIGHLIGHT1: defStyle.CSS_CELL_HIGHLIGHT1,
                /**
                * @property Style.CSS_CELL_HIGHLIGHT2
                */
                CSS_CELL_HIGHLIGHT2: defStyle.CSS_CELL_HIGHLIGHT2,
                /**
                * @property Style.CSS_CELL_HIGHLIGHT3
                */
                CSS_CELL_HIGHLIGHT3: defStyle.CSS_CELL_HIGHLIGHT3,
                /**
                * @property Style.CSS_CELL_HIGHLIGHT4
                */
                CSS_CELL_HIGHLIGHT4: defStyle.CSS_CELL_HIGHLIGHT4
            };
        },

        /**
        * Builds the date label that will be displayed in the calendar header or
        * footer, depending on configuration.
        * @method buildMonthLabel
        * @return	{String}	The formatted calendar month label
        */
        buildMonthLabel: function() {
            return this._buildMonthLabel(this.cfg.getProperty(DEF_CFG.PAGEDATE.key));
        },

        /**
        * Helper method, to format a Month Year string, given a JavaScript Date, based on the 
        * Calendar localization settings
        * 
        * @method _buildMonthLabel
        * @private
        * @param {Date} date
        * @return {String} Formated month, year string
        */
        _buildMonthLabel: function(date) {
            var monthLabel = this.Locale.LOCALE_MONTHS[date.getMonth()] + this.Locale.MY_LABEL_MONTH_SUFFIX,
			yearLabel = date.getFullYear() + this.Locale.MY_LABEL_YEAR_SUFFIX;

            if (this.Locale.MY_LABEL_MONTH_POSITION == 2 || this.Locale.MY_LABEL_YEAR_POSITION == 1) {
                return yearLabel + monthLabel;
            } else {
                return monthLabel + yearLabel;
            }
        },

        /**
        * Builds the date digit that will be displayed in calendar cells
        * @method buildDayLabel
        * @param {Date}	workingDate	The current working date
        * @return	{String}	The formatted day label
        */
        buildDayLabel: function(workingDate) {
            return workingDate.getDate();
        },

        /**
        * Creates the title bar element and adds it to Calendar container DIV
        * 
        * @method createTitleBar
        * @param {String} strTitle The title to display in the title bar
        * @return The title bar element
        */
        createTitleBar: function(strTitle) {
            var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || document.createElement("div");
            tDiv.className = YAHOO.widget.CalendarGroup.CSS_2UPTITLE;
            tDiv.innerHTML = strTitle;
            this.oDomContainer.insertBefore(tDiv, this.oDomContainer.firstChild);

            Dom.addClass(this.oDomContainer, "withtitle");

            return tDiv;
        },

        /**
        * Removes the title bar element from the DOM
        * 
        * @method removeTitleBar
        */
        removeTitleBar: function() {
            var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || null;
            if (tDiv) {
                Event.purgeElement(tDiv);
                this.oDomContainer.removeChild(tDiv);
            }
            Dom.removeClass(this.oDomContainer, "withtitle");
        },

        /**
        * Creates the close button HTML element and adds it to Calendar container DIV
        * 
        * @method createCloseButton
        * @return The close HTML element created
        */
        createCloseButton: function() {
            var cssClose = YAHOO.widget.CalendarGroup.CSS_2UPCLOSE,
			DEPR_CLOSE_PATH = "us/my/bn/x_d.gif",
			lnk = Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0],
			strings = this.cfg.getProperty(DEF_CFG.STRINGS.key),
			closeStr = (strings && strings.close) ? strings.close : "";

            if (!lnk) {
                lnk = document.createElement("a");
                Event.addListener(lnk, "click", function(e, cal) {
                    cal.hide();
                    Event.preventDefault(e);
                }, this);
            }

            lnk.href = "#";
            lnk.className = "link-close";

            if (Calendar.IMG_ROOT !== null) {
                var img = Dom.getElementsByClassName(cssClose, "img", lnk)[0] || document.createElement("img");
                img.src = Calendar.IMG_ROOT + DEPR_CLOSE_PATH;
                img.className = cssClose;
                lnk.appendChild(img);
            } else {
                lnk.innerHTML = '<span class="' + cssClose + ' ' + this.Style.CSS_CLOSE + '">' + closeStr + '</span>';
            }
            this.oDomContainer.appendChild(lnk);

            return lnk;
        },

        /**
        * Removes the close button HTML element from the DOM
        * 
        * @method removeCloseButton
        */
        removeCloseButton: function() {
            var btn = Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0] || null;
            if (btn) {
                Event.purgeElement(btn);
                this.oDomContainer.removeChild(btn);
            }
        },

        /**
        * Renders the calendar header.
        * @method renderHeader
        * @param {Array}	html	The current working HTML array
        * @return {Array} The current working HTML array
        */
        renderHeader: function(html) {


            var colSpan = 7,
			DEPR_NAV_LEFT = "us/tr/callt.gif",
			DEPR_NAV_RIGHT = "us/tr/calrt.gif",
			cfg = this.cfg,
			pageDate = cfg.getProperty(DEF_CFG.PAGEDATE.key),
			strings = cfg.getProperty(DEF_CFG.STRINGS.key),
			prevStr = (strings && strings.previousMonth) ? strings.previousMonth : "",
			nextStr = (strings && strings.nextMonth) ? strings.nextMonth : "",
            monthLabel;

            if (cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) {
                colSpan += 1;
            }

            if (cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) {
                colSpan += 1;
            }

            html[html.length] = "<thead>";
            html[html.length] = "<tr>";
            html[html.length] = '<th colspan="' + colSpan + '" class="' + this.Style.CSS_HEADER_TEXT + '">';
            html[html.length] = '<div class="' + this.Style.CSS_HEADER + '">';

            var renderLeft, renderRight = false;

            if (this.parent) {
                if (this.index === 0) {
                    renderLeft = true;
                }
                if (this.index == (this.parent.cfg.getProperty("pages") - 1)) {
                    renderRight = true;
                }
            } else {
                renderLeft = true;
                renderRight = true;
            }

            if (renderLeft) {
                monthLabel = this._buildMonthLabel(DateMath.subtract(pageDate, DateMath.MONTH, 1));

                var leftArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_LEFT.key);
                // Check for deprecated customization - If someone set IMG_ROOT, but didn't set NAV_ARROW_LEFT, then set NAV_ARROW_LEFT to the old deprecated value
                if (leftArrow === null && Calendar.IMG_ROOT !== null) {
                    leftArrow = Calendar.IMG_ROOT + DEPR_NAV_LEFT;
                }
                var leftStyle = (leftArrow === null) ? "" : ' style="background-image:url(' + leftArrow + ')"';
                html[html.length] = '<a class="' + this.Style.CSS_NAV_LEFT + '"' + leftStyle + ' href="#">' + prevStr + ' (' + monthLabel + ')' + '</a>';
            }

            var lbl = this.buildMonthLabel();
            var cal = this.parent || this;
            if (cal.cfg.getProperty("navigator")) {
                lbl = "<a class=\"" + this.Style.CSS_NAV + "\" href=\"#\">" + lbl + "</a>";
            }
            html[html.length] = lbl;

            if (renderRight) {
                monthLabel = this._buildMonthLabel(DateMath.add(pageDate, DateMath.MONTH, 1));

                var rightArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_RIGHT.key);
                if (rightArrow === null && Calendar.IMG_ROOT !== null) {
                    rightArrow = Calendar.IMG_ROOT + DEPR_NAV_RIGHT;
                }
                var rightStyle = (rightArrow === null) ? "" : ' style="background-image:url(' + rightArrow + ')"';
                html[html.length] = '<a class="' + this.Style.CSS_NAV_RIGHT + '"' + rightStyle + ' href="#">' + nextStr + ' (' + monthLabel + ')' + '</a>';
            }

            html[html.length] = '</div>\n</th>\n</tr>';

            if (cfg.getProperty(DEF_CFG.SHOW_WEEKDAYS.key)) {
                html = this.buildWeekdays(html);
            }

            html[html.length] = '</thead>';

            return html;
        },

        /**
        * Renders the Calendar's weekday headers.
        * @method buildWeekdays
        * @param {Array}	html	The current working HTML array
        * @return {Array} The current working HTML array
        */
        buildWeekdays: function(html) {

            html[html.length] = '<tr class="' + this.Style.CSS_WEEKDAY_ROW + '">';

            if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) {
                html[html.length] = '<th>&#160;</th>';
            }

            for (var i = 0; i < this.Locale.LOCALE_WEEKDAYS.length; ++i) {
                html[html.length] = '<th class="calweekdaycell">' + this.Locale.LOCALE_WEEKDAYS[i] + '</th>';
            }

            if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) {
                html[html.length] = '<th>&#160;</th>';
            }

            html[html.length] = '</tr>';

            return html;
        },

        /**
        * Renders the calendar body.
        * @method renderBody
        * @param {Date}	workingDate	The current working Date being used for the render process
        * @param {Array}	html	The current working HTML array
        * @return {Array} The current working HTML array
        */
        renderBody: function(workingDate, html) {

            var startDay = this.cfg.getProperty(DEF_CFG.START_WEEKDAY.key);

            this.preMonthDays = workingDate.getDay();
            if (startDay > 0) {
                this.preMonthDays -= startDay;
            }
            if (this.preMonthDays < 0) {
                this.preMonthDays += 7;
            }

            this.monthDays = DateMath.findMonthEnd(workingDate).getDate();
            this.postMonthDays = Calendar.DISPLAY_DAYS - this.preMonthDays - this.monthDays;


            workingDate = DateMath.subtract(workingDate, DateMath.DAY, this.preMonthDays);

            var weekNum,
			weekClass,
			weekPrefix = "w",
			cellPrefix = "_cell",
			workingDayPrefix = "wd",
			dayPrefix = "d",
			cellRenderers,
			renderer,
			t = this.today,
			cfg = this.cfg,
			todayYear = t.getFullYear(),
			todayMonth = t.getMonth(),
			todayDate = t.getDate(),
			useDate = cfg.getProperty(DEF_CFG.PAGEDATE.key),
			hideBlankWeeks = cfg.getProperty(DEF_CFG.HIDE_BLANK_WEEKS.key),
			showWeekFooter = cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key),
			showWeekHeader = cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key),
			mindate = cfg.getProperty(DEF_CFG.MINDATE.key),
			maxdate = cfg.getProperty(DEF_CFG.MAXDATE.key);

            if (mindate) {
                mindate = DateMath.clearTime(mindate);
            }
            if (maxdate) {
                maxdate = DateMath.clearTime(maxdate);
            }

            html[html.length] = '<tbody class="m' + (useDate.getMonth() + 1) + ' ' + this.Style.CSS_BODY + '">';

            var i = 0,
			tempDiv = document.createElement("div"),
			cell = document.createElement("td");

            tempDiv.appendChild(cell);

            var cal = this.parent || this;

            for (var r = 0; r < 6; r++) {
                weekNum = DateMath.getWeekNumber(workingDate, startDay);
                weekClass = weekPrefix + weekNum;

                // Local OOM check for performance, since we already have pagedate
                if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) {
                    break;
                } else {
                    html[html.length] = '<tr class="' + weekClass + '">';

                    if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }

                    for (var d = 0; d < 7; d++) { // Render actual days

                        cellRenderers = [];

                        this.clearElement(cell);
                        cell.className = this.Style.CSS_CELL;
                        cell.id = this.id + cellPrefix + i;

                        if (workingDate.getDate() == todayDate &&
						workingDate.getMonth() == todayMonth &&
						workingDate.getFullYear() == todayYear) {
                            cellRenderers[cellRenderers.length] = cal.renderCellStyleToday;
                        }

                        var workingArray = [workingDate.getFullYear(), workingDate.getMonth() + 1, workingDate.getDate()];
                        this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates

                        // Local OOM check for performance, since we already have pagedate
                        if (workingDate.getMonth() != useDate.getMonth()) {
                            cellRenderers[cellRenderers.length] = cal.renderCellNotThisMonth;
                        } else {
                            Dom.addClass(cell, workingDayPrefix + workingDate.getDay());
                            Dom.addClass(cell, dayPrefix + workingDate.getDate());

                            for (var s = 0; s < this.renderStack.length; ++s) {

                                renderer = null;

                                var rArray = this.renderStack[s],
								type = rArray[0],
								month,
								day,
								year;

                                switch (type) {
                                    case Calendar.DATE:
                                        month = rArray[1][1];
                                        day = rArray[1][2];
                                        year = rArray[1][0];

                                        if (workingDate.getMonth() + 1 == month && workingDate.getDate() == day && workingDate.getFullYear() == year) {
                                            renderer = rArray[2];
                                            this.renderStack.splice(s, 1);
                                        }
                                        break;
                                    case Calendar.MONTH_DAY:
                                        month = rArray[1][0];
                                        day = rArray[1][1];

                                        if (workingDate.getMonth() + 1 == month && workingDate.getDate() == day) {
                                            renderer = rArray[2];
                                            this.renderStack.splice(s, 1);
                                        }
                                        break;
                                    case Calendar.RANGE:
                                        var date1 = rArray[1][0],
										date2 = rArray[1][1],
										d1month = date1[1],
										d1day = date1[2],
										d1year = date1[0],
										d1 = DateMath.getDate(d1year, d1month - 1, d1day),
										d2month = date2[1],
										d2day = date2[2],
										d2year = date2[0],
										d2 = DateMath.getDate(d2year, d2month - 1, d2day);

                                        if (workingDate.getTime() >= d1.getTime() && workingDate.getTime() <= d2.getTime()) {
                                            renderer = rArray[2];

                                            if (workingDate.getTime() == d2.getTime()) {
                                                this.renderStack.splice(s, 1);
                                            }
                                        }
                                        break;
                                    case Calendar.WEEKDAY:
                                        var weekday = rArray[1][0];
                                        if (workingDate.getDay() + 1 == weekday) {
                                            renderer = rArray[2];
                                        }
                                        break;
                                    case Calendar.MONTH:
                                        month = rArray[1][0];
                                        if (workingDate.getMonth() + 1 == month) {
                                            renderer = rArray[2];
                                        }
                                        break;
                                }

                                if (renderer) {
                                    cellRenderers[cellRenderers.length] = renderer;
                                }
                            }

                        }

                        if (this._indexOfSelectedFieldArray(workingArray) > -1) {
                            cellRenderers[cellRenderers.length] = cal.renderCellStyleSelected;
                        }

                        if ((mindate && (workingDate.getTime() < mindate.getTime())) ||
						(maxdate && (workingDate.getTime() > maxdate.getTime()))
					) {
                            cellRenderers[cellRenderers.length] = cal.renderOutOfBoundsDate;
                        } else {
                            cellRenderers[cellRenderers.length] = cal.styleCellDefault;
                            cellRenderers[cellRenderers.length] = cal.renderCellDefault;
                        }

                        for (var x = 0; x < cellRenderers.length; ++x) {
                            if (cellRenderers[x].call(cal, workingDate, cell) == Calendar.STOP_RENDER) {
                                break;
                            }
                        }

                        workingDate.setTime(workingDate.getTime() + DateMath.ONE_DAY_MS);
                        // Just in case we crossed DST/Summertime boundaries
                        workingDate = DateMath.clearTime(workingDate);

                        if (i >= 0 && i <= 6) {
                            Dom.addClass(cell, this.Style.CSS_CELL_TOP);
                        }
                        if ((i % 7) === 0) {
                            Dom.addClass(cell, this.Style.CSS_CELL_LEFT);
                        }
                        if (((i + 1) % 7) === 0) {
                            Dom.addClass(cell, this.Style.CSS_CELL_RIGHT);
                        }

                        var postDays = this.postMonthDays;
                        if (hideBlankWeeks && postDays >= 7) {
                            var blankWeeks = Math.floor(postDays / 7);
                            for (var p = 0; p < blankWeeks; ++p) {
                                postDays -= 7;
                            }
                        }

                        if (i >= ((this.preMonthDays + postDays + this.monthDays) - 7)) {
                            Dom.addClass(cell, this.Style.CSS_CELL_BOTTOM);
                        }

                        html[html.length] = tempDiv.innerHTML;
                        i++;
                    }

                    if (showWeekFooter) { html = this.renderRowFooter(weekNum, html); }

                    html[html.length] = '</tr>';
                }
            }

            html[html.length] = '</tbody>';

            return html;
        },

        /**
        * Renders the calendar footer. In the default implementation, there is
        * no footer.
        * @method renderFooter
        * @param {Array}	html	The current working HTML array
        * @return {Array} The current working HTML array
        */
        renderFooter: function(html) { return html; },

        /**
        * Renders the calendar after it has been configured. The render() method has a specific call chain that will execute
        * when the method is called: renderHeader, renderBody, renderFooter.
        * Refer to the documentation for those methods for information on 
        * individual render tasks.
        * @method render
        */
        render: function() {
            this.beforeRenderEvent.fire();

            // Find starting day of the current month
            var workingDate = DateMath.findMonthStart(this.cfg.getProperty(DEF_CFG.PAGEDATE.key));

            this.resetRenderers();
            this.cellDates.length = 0;

            Event.purgeElement(this.oDomContainer, true);

            var html = [];

            html[html.length] = '<table cellSpacing="0" class="' + this.Style.CSS_CALENDAR + ' y' + workingDate.getFullYear() + '" id="' + this.id + '">';
            html = this.renderHeader(html);
            html = this.renderBody(workingDate, html);
            html = this.renderFooter(html);
            html[html.length] = '</table>';

            this.oDomContainer.innerHTML = html.join("\n");

            this.applyListeners();
            this.cells = this.oDomContainer.getElementsByTagName("td");

            this.cfg.refireEvent(DEF_CFG.TITLE.key);
            this.cfg.refireEvent(DEF_CFG.CLOSE.key);
            this.cfg.refireEvent(DEF_CFG.IFRAME.key);

            this.renderEvent.fire();
        },

        /**
        * Applies the Calendar's DOM listeners to applicable elements.
        * @method applyListeners
        */
        applyListeners: function() {
            var root = this.oDomContainer,
			cal = this.parent || this,
			anchor = "a",
			click = "click";

            var linkLeft = Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, anchor, root),
			linkRight = Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, anchor, root);

            if (linkLeft && linkLeft.length > 0) {
                this.linkLeft = linkLeft[0];
                Event.addListener(this.linkLeft, click, this.doPreviousMonthNav, cal, true);
            }

            if (linkRight && linkRight.length > 0) {
                this.linkRight = linkRight[0];
                Event.addListener(this.linkRight, click, this.doNextMonthNav, cal, true);
            }

            if (cal.cfg.getProperty("navigator") !== null) {
                this.applyNavListeners();
            }

            if (this.domEventMap) {
                var el, elements;
                for (var cls in this.domEventMap) {
                    if (Lang.hasOwnProperty(this.domEventMap, cls)) {
                        var items = this.domEventMap[cls];

                        if (!(items instanceof Array)) {
                            items = [items];
                        }

                        for (var i = 0; i < items.length; i++) {
                            var item = items[i];
                            elements = Dom.getElementsByClassName(cls, item.tag, this.oDomContainer);

                            for (var c = 0; c < elements.length; c++) {
                                el = elements[c];
                                Event.addListener(el, item.event, item.handler, item.scope, item.correct);
                            }
                        }
                    }
                }
            }

            Event.addListener(this.oDomContainer, "click", this.doSelectCell, this);
            Event.addListener(this.oDomContainer, "mouseover", this.doCellMouseOver, this);
            Event.addListener(this.oDomContainer, "mouseout", this.doCellMouseOut, this);
        },

        applyNavListeners: function() {
            var calParent = this.parent || this,
			cal = this,
			navBtns = Dom.getElementsByClassName(this.Style.CSS_NAV, "a", this.oDomContainer);

            if (navBtns.length > 0) {

                Event.addListener(navBtns, "click", function(e, obj) {
                    var target = Event.getTarget(e);
                    // this == navBtn
                    if (this === target || Dom.isAncestor(this, target)) {
                        Event.preventDefault(e);
                    }
                    var navigator = calParent.oNavigator;
                    if (navigator) {
                        var pgdate = cal.cfg.getProperty("pagedate");
                        navigator.setYear(pgdate.getFullYear());
                        navigator.setMonth(pgdate.getMonth());
                        navigator.show();
                    }
                });
            }
        },

        /**
        * Retrieves the Date object for the specified Calendar cell
        * @method getDateByCellId
        * @param {String}	id	The id of the cell
        * @return {Date} The Date object for the specified Calendar cell
        */
        getDateByCellId: function(id) {
            var date = this.getDateFieldsByCellId(id);
            return (date) ? DateMath.getDate(date[0], date[1] - 1, date[2]) : null;
        },

        /**
        * Retrieves the Date object for the specified Calendar cell
        * @method getDateFieldsByCellId
        * @param {String}	id	The id of the cell
        * @return {Array}	The array of Date fields for the specified Calendar cell
        */
        getDateFieldsByCellId: function(id) {
            id = this.getIndexFromId(id);
            return (id > -1) ? this.cellDates[id] : null;
        },

        /**
        * Find the Calendar's cell index for a given date.
        * If the date is not found, the method returns -1.
        * <p>
        * The returned index can be used to lookup the cell HTMLElement  
        * using the Calendar's cells array or passed to selectCell to select 
        * cells by index. 
        * </p>
        *
        * See <a href="#cells">cells</a>, <a href="#selectCell">selectCell</a>.
        *
        * @method getCellIndex
        * @param {Date} date JavaScript Date object, for which to find a cell index.
        * @return {Number} The index of the date in Calendars cellDates/cells arrays, or -1 if the date 
        * is not on the curently rendered Calendar page.
        */
        getCellIndex: function(date) {
            var idx = -1;
            if (date) {
                var m = date.getMonth(),
				y = date.getFullYear(),
				d = date.getDate(),
				dates = this.cellDates;

                for (var i = 0; i < dates.length; ++i) {
                    var cellDate = dates[i];
                    if (cellDate[0] === y && cellDate[1] === m + 1 && cellDate[2] === d) {
                        idx = i;
                        break;
                    }
                }
            }
            return idx;
        },

        /**
        * Given the id used to mark each Calendar cell, this method
        * extracts the index number from the id.
        * 
        * @param {String} strId The cell id
        * @return {Number} The index of the cell, or -1 if id does not contain an index number
        */
        getIndexFromId: function(strId) {
            var idx = -1,
			li = strId.lastIndexOf("_cell");

            if (li > -1) {
                idx = parseInt(strId.substring(li + 5), 10);
            }

            return idx;
        },

        // BEGIN BUILT-IN TABLE CELL RENDERERS

        /**
        * Renders a cell that falls before the minimum date or after the maximum date.
        * widget class.
        * @method renderOutOfBoundsDate
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
        *			should not be terminated
        */
        renderOutOfBoundsDate: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_OOB);
            cell.innerHTML = workingDate.getDate();
            return Calendar.STOP_RENDER;
        },

        /**
        * Renders the row header for a week.
        * @method renderRowHeader
        * @param {Number}	weekNum	The week number of the current row
        * @param {Array}	cell	The current working HTML array
        */
        renderRowHeader: function(weekNum, html) {
            html[html.length] = '<th class="calrowhead">' + weekNum + '</th>';
            return html;
        },

        /**
        * Renders the row footer for a week.
        * @method renderRowFooter
        * @param {Number}	weekNum	The week number of the current row
        * @param {Array}	cell	The current working HTML array
        */
        renderRowFooter: function(weekNum, html) {
            html[html.length] = '<th class="calrowfoot">' + weekNum + '</th>';
            return html;
        },

        /**
        * Renders a single standard calendar cell in the calendar widget table.
        * All logic for determining how a standard default cell will be rendered is 
        * encapsulated in this method, and must be accounted for when extending the
        * widget class.
        * @method renderCellDefault
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        renderCellDefault: function(workingDate, cell) {
            cell.innerHTML = '<a href="#" class="' + this.Style.CSS_CELL_SELECTOR + '">' + this.buildDayLabel(workingDate) + "</a>";
        },

        /**
        * Styles a selectable cell.
        * @method styleCellDefault
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        styleCellDefault: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE);
        },


        /**
        * Renders a single standard calendar cell using the CSS hightlight1 style
        * @method renderCellStyleHighlight1
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        renderCellStyleHighlight1: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1);
        },

        /**
        * Renders a single standard calendar cell using the CSS hightlight2 style
        * @method renderCellStyleHighlight2
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        renderCellStyleHighlight2: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2);
        },

        /**
        * Renders a single standard calendar cell using the CSS hightlight3 style
        * @method renderCellStyleHighlight3
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        renderCellStyleHighlight3: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3);
        },

        /**
        * Renders a single standard calendar cell using the CSS hightlight4 style
        * @method renderCellStyleHighlight4
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        renderCellStyleHighlight4: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4);
        },

        /**
        * Applies the default style used for rendering today's date to the current calendar cell
        * @method renderCellStyleToday
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        */
        renderCellStyleToday: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_TODAY);
        },

        /**
        * Applies the default style used for rendering selected dates to the current calendar cell
        * @method renderCellStyleSelected
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
        *			should not be terminated
        */
        renderCellStyleSelected: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_SELECTED);
        },

        /**
        * Applies the default style used for rendering dates that are not a part of the current
        * month (preceding or trailing the cells for the current month)
        * @method renderCellNotThisMonth
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
        *			should not be terminated
        */
        renderCellNotThisMonth: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL_OOM);
            cell.innerHTML = workingDate.getDate();
            return Calendar.STOP_RENDER;
        },

        /**
        * Renders the current calendar cell as a non-selectable "black-out" date using the default
        * restricted style.
        * @method renderBodyCellRestricted
        * @param {Date}					workingDate		The current working Date object being used to generate the calendar
        * @param {HTMLTableCellElement}	cell			The current working cell in the calendar
        * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
        *			should not be terminated
        */
        renderBodyCellRestricted: function(workingDate, cell) {
            Dom.addClass(cell, this.Style.CSS_CELL);
            Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED);
            cell.innerHTML = workingDate.getDate();
            return Calendar.STOP_RENDER;
        },

        // END BUILT-IN TABLE CELL RENDERERS

        // BEGIN MONTH NAVIGATION METHODS

        /**
        * Adds the designated number of months to the current calendar month, and sets the current
        * calendar page date to the new month.
        * @method addMonths
        * @param {Number}	count	The number of months to add to the current calendar
        */
        addMonths: function(count) {
            var cfgPageDate = DEF_CFG.PAGEDATE.key;
            this.cfg.setProperty(cfgPageDate, DateMath.add(this.cfg.getProperty(cfgPageDate), DateMath.MONTH, count));
            this.resetRenderers();
            this.changePageEvent.fire();
        },

        /**
        * Subtracts the designated number of months from the current calendar month, and sets the current
        * calendar page date to the new month.
        * @method subtractMonths
        * @param {Number}	count	The number of months to subtract from the current calendar
        */
        subtractMonths: function(count) {
            var cfgPageDate = DEF_CFG.PAGEDATE.key;
            this.cfg.setProperty(cfgPageDate, DateMath.subtract(this.cfg.getProperty(cfgPageDate), DateMath.MONTH, count));
            this.resetRenderers();
            this.changePageEvent.fire();
        },

        /**
        * Adds the designated number of years to the current calendar, and sets the current
        * calendar page date to the new month.
        * @method addYears
        * @param {Number}	count	The number of years to add to the current calendar
        */
        addYears: function(count) {
            var cfgPageDate = DEF_CFG.PAGEDATE.key;
            this.cfg.setProperty(cfgPageDate, DateMath.add(this.cfg.getProperty(cfgPageDate), DateMath.YEAR, count));
            this.resetRenderers();
            this.changePageEvent.fire();
        },

        /**
        * Subtcats the designated number of years from the current calendar, and sets the current
        * calendar page date to the new month.
        * @method subtractYears
        * @param {Number}	count	The number of years to subtract from the current calendar
        */
        subtractYears: function(count) {
            var cfgPageDate = DEF_CFG.PAGEDATE.key;
            this.cfg.setProperty(cfgPageDate, DateMath.subtract(this.cfg.getProperty(cfgPageDate), DateMath.YEAR, count));
            this.resetRenderers();
            this.changePageEvent.fire();
        },

        /**
        * Navigates to the next month page in the calendar widget.
        * @method nextMonth
        */
        nextMonth: function() {
            this.addMonths(1);
        },

        /**
        * Navigates to the previous month page in the calendar widget.
        * @method previousMonth
        */
        previousMonth: function() {
            this.subtractMonths(1);
        },

        /**
        * Navigates to the next year in the currently selected month in the calendar widget.
        * @method nextYear
        */
        nextYear: function() {
            this.addYears(1);
        },

        /**
        * Navigates to the previous year in the currently selected month in the calendar widget.
        * @method previousYear
        */
        previousYear: function() {
            this.subtractYears(1);
        },

        // END MONTH NAVIGATION METHODS

        // BEGIN SELECTION METHODS

        /**
        * Resets the calendar widget to the originally selected month and year, and 
        * sets the calendar to the initial selection(s).
        * @method reset
        */
        reset: function() {
            this.cfg.resetProperty(DEF_CFG.SELECTED.key);
            this.cfg.resetProperty(DEF_CFG.PAGEDATE.key);
            this.resetEvent.fire();
        },

        /**
        * Clears the selected dates in the current calendar widget and sets the calendar
        * to the current month and year.
        * @method clear
        */
        clear: function() {
            this.cfg.setProperty(DEF_CFG.SELECTED.key, []);
            this.cfg.setProperty(DEF_CFG.PAGEDATE.key, new Date(this.today.getTime()));
            this.clearEvent.fire();
        },

        /**
        * Selects a date or a collection of dates on the current calendar. This method, by default,
        * does not call the render method explicitly. Once selection has completed, render must be 
        * called for the changes to be reflected visually.
        *
        * Any dates which are OOB (out of bounds, not selectable) will not be selected and the array of 
        * selected dates passed to the selectEvent will not contain OOB dates.
        * 
        * If all dates are OOB, the no state change will occur; beforeSelect and select events will not be fired.
        *
        * @method select
        * @param	{String/Date/Date[]}	date	The date string of dates to select in the current calendar. Valid formats are
        *								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
        *								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
        *								This method can also take a JavaScript Date object or an array of Date objects.
        * @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        select: function(date) {

            var aToBeSelected = this._toFieldArray(date),
			validDates = [],
			selected = [],
			cfgSelected = DEF_CFG.SELECTED.key;


            for (var a = 0; a < aToBeSelected.length; ++a) {
                var toSelect = aToBeSelected[a];

                if (!this.isDateOOB(this._toDate(toSelect))) {

                    if (validDates.length === 0) {
                        this.beforeSelectEvent.fire();
                        selected = this.cfg.getProperty(cfgSelected);
                    }
                    validDates.push(toSelect);

                    if (this._indexOfSelectedFieldArray(toSelect) == -1) {
                        selected[selected.length] = toSelect;
                    }
                }
            }


            if (validDates.length > 0) {
                if (this.parent) {
                    this.parent.cfg.setProperty(cfgSelected, selected);
                } else {
                    this.cfg.setProperty(cfgSelected, selected);
                }
                this.selectEvent.fire(validDates);
            }

            return this.getSelectedDates();
        },

        /**
        * Selects a date on the current calendar by referencing the index of the cell that should be selected.
        * This method is used to easily select a single cell (usually with a mouse click) without having to do
        * a full render. The selected style is applied to the cell directly.
        *
        * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month 
        * or out of bounds cells), it will not be selected and in such a case beforeSelect and select events will not be fired.
        * 
        * @method selectCell
        * @param	{Number}	cellIndex	The index of the cell to select in the current calendar. 
        * @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        selectCell: function(cellIndex) {

            var cell = this.cells[cellIndex],
			cellDate = this.cellDates[cellIndex],
			dCellDate = this._toDate(cellDate),
			selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);


            if (selectable) {

                this.beforeSelectEvent.fire();

                var cfgSelected = DEF_CFG.SELECTED.key;
                var selected = this.cfg.getProperty(cfgSelected);

                var selectDate = cellDate.concat();

                if (this._indexOfSelectedFieldArray(selectDate) == -1) {
                    selected[selected.length] = selectDate;
                }
                if (this.parent) {
                    this.parent.cfg.setProperty(cfgSelected, selected);
                } else {
                    this.cfg.setProperty(cfgSelected, selected);
                }
                this.renderCellStyleSelected(dCellDate, cell);
                this.selectEvent.fire([selectDate]);

                this.doCellMouseOut.call(cell, null, this);
            }

            return this.getSelectedDates();
        },

        /**
        * Deselects a date or a collection of dates on the current calendar. This method, by default,
        * does not call the render method explicitly. Once deselection has completed, render must be 
        * called for the changes to be reflected visually.
        * 
        * The method will not attempt to deselect any dates which are OOB (out of bounds, and hence not selectable) 
        * and the array of deselected dates passed to the deselectEvent will not contain any OOB dates.
        * 
        * If all dates are OOB, beforeDeselect and deselect events will not be fired.
        * 
        * @method deselect
        * @param	{String/Date/Date[]}	date	The date string of dates to deselect in the current calendar. Valid formats are
        *								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
        *								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
        *								This method can also take a JavaScript Date object or an array of Date objects.	
        * @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        deselect: function(date) {

            var aToBeDeselected = this._toFieldArray(date),
			validDates = [],
			selected = [],
			cfgSelected = DEF_CFG.SELECTED.key;


            for (var a = 0; a < aToBeDeselected.length; ++a) {
                var toDeselect = aToBeDeselected[a];

                if (!this.isDateOOB(this._toDate(toDeselect))) {

                    if (validDates.length === 0) {
                        this.beforeDeselectEvent.fire();
                        selected = this.cfg.getProperty(cfgSelected);
                    }

                    validDates.push(toDeselect);

                    var index = this._indexOfSelectedFieldArray(toDeselect);
                    if (index != -1) {
                        selected.splice(index, 1);
                    }
                }
            }


            if (validDates.length > 0) {
                if (this.parent) {
                    this.parent.cfg.setProperty(cfgSelected, selected);
                } else {
                    this.cfg.setProperty(cfgSelected, selected);
                }
                this.deselectEvent.fire(validDates);
            }

            return this.getSelectedDates();
        },

        /**
        * Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
        * This method is used to easily deselect a single cell (usually with a mouse click) without having to do
        * a full render. The selected style is removed from the cell directly.
        * 
        * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month 
        * or out of bounds cells), the method will not attempt to deselect it and in such a case, beforeDeselect and 
        * deselect events will not be fired.
        * 
        * @method deselectCell
        * @param	{Number}	cellIndex	The index of the cell to deselect in the current calendar. 
        * @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        deselectCell: function(cellIndex) {
            var cell = this.cells[cellIndex],
			cellDate = this.cellDates[cellIndex],
			cellDateIndex = this._indexOfSelectedFieldArray(cellDate);

            var selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);

            if (selectable) {

                this.beforeDeselectEvent.fire();

                var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key),
				dCellDate = this._toDate(cellDate),
				selectDate = cellDate.concat();

                if (cellDateIndex > -1) {
                    if (this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth() == dCellDate.getMonth() &&
					this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getFullYear() == dCellDate.getFullYear()) {
                        Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED);
                    }
                    selected.splice(cellDateIndex, 1);
                }

                if (this.parent) {
                    this.parent.cfg.setProperty(DEF_CFG.SELECTED.key, selected);
                } else {
                    this.cfg.setProperty(DEF_CFG.SELECTED.key, selected);
                }

                this.deselectEvent.fire(selectDate);
            }

            return this.getSelectedDates();
        },

        /**
        * Deselects all dates on the current calendar.
        * @method deselectAll
        * @return {Date[]}		Array of JavaScript Date objects representing all individual dates that are currently selected.
        *						Assuming that this function executes properly, the return value should be an empty array.
        *						However, the empty array is returned for the sake of being able to check the selection status
        *						of the calendar.
        */
        deselectAll: function() {
            this.beforeDeselectEvent.fire();

            var cfgSelected = DEF_CFG.SELECTED.key,
			selected = this.cfg.getProperty(cfgSelected),
			count = selected.length,
			sel = selected.concat();

            if (this.parent) {
                this.parent.cfg.setProperty(cfgSelected, []);
            } else {
                this.cfg.setProperty(cfgSelected, []);
            }

            if (count > 0) {
                this.deselectEvent.fire(sel);
            }

            return this.getSelectedDates();
        },

        // END SELECTION METHODS

        // BEGIN TYPE CONVERSION METHODS

        /**
        * Converts a date (either a JavaScript Date object, or a date string) to the internal data structure
        * used to represent dates: [[yyyy,mm,dd],[yyyy,mm,dd]].
        * @method _toFieldArray
        * @private
        * @param	{String/Date/Date[]}	date	The date string of dates to deselect in the current calendar. Valid formats are
        *								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
        *								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
        *								This method can also take a JavaScript Date object or an array of Date objects.	
        * @return {Array[](Number[])}	Array of date field arrays
        */
        _toFieldArray: function(date) {
            var returnDate = [];

            if (date instanceof Date) {
                returnDate = [[date.getFullYear(), date.getMonth() + 1, date.getDate()]];
            } else if (Lang.isString(date)) {
                returnDate = this._parseDates(date);
            } else if (Lang.isArray(date)) {
                for (var i = 0; i < date.length; ++i) {
                    var d = date[i];
                    returnDate[returnDate.length] = [d.getFullYear(), d.getMonth() + 1, d.getDate()];
                }
            }

            return returnDate;
        },

        /**
        * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object. The date field array
        * is the format in which dates are as provided as arguments to selectEvent and deselectEvent listeners.
        * 
        * @method toDate
        * @param	{Number[]}	dateFieldArray	The date field array to convert to a JavaScript Date.
        * @return	{Date}	JavaScript Date object representing the date field array.
        */
        toDate: function(dateFieldArray) {
            return this._toDate(dateFieldArray);
        },

        /**
        * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object.
        * @method _toDate
        * @private
        * @deprecated Made public, toDate 
        * @param	{Number[]}		dateFieldArray	The date field array to convert to a JavaScript Date.
        * @return	{Date}	JavaScript Date object representing the date field array
        */
        _toDate: function(dateFieldArray) {
            if (dateFieldArray instanceof Date) {
                return dateFieldArray;
            } else {
                return DateMath.getDate(dateFieldArray[0], dateFieldArray[1] - 1, dateFieldArray[2]);
            }
        },

        // END TYPE CONVERSION METHODS 

        // BEGIN UTILITY METHODS

        /**
        * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object.
        * @method _fieldArraysAreEqual
        * @private
        * @param	{Number[]}	array1	The first date field array to compare
        * @param	{Number[]}	array2	The first date field array to compare
        * @return	{Boolean}	The boolean that represents the equality of the two arrays
        */
        _fieldArraysAreEqual: function(array1, array2) {
            var match = false;

            if (array1[0] == array2[0] && array1[1] == array2[1] && array1[2] == array2[2]) {
                match = true;
            }

            return match;
        },

        /**
        * Gets the index of a date field array [yyyy,mm,dd] in the current list of selected dates.
        * @method	_indexOfSelectedFieldArray
        * @private
        * @param	{Number[]}		find	The date field array to search for
        * @return	{Number}			The index of the date field array within the collection of selected dates.
        *								-1 will be returned if the date is not found.
        */
        _indexOfSelectedFieldArray: function(find) {
            var selected = -1,
			seldates = this.cfg.getProperty(DEF_CFG.SELECTED.key);

            for (var s = 0; s < seldates.length; ++s) {
                var sArray = seldates[s];
                if (find[0] == sArray[0] && find[1] == sArray[1] && find[2] == sArray[2]) {
                    selected = s;
                    break;
                }
            }

            return selected;
        },

        /**
        * Determines whether a given date is OOM (out of month).
        * @method	isDateOOM
        * @param	{Date}	date	The JavaScript Date object for which to check the OOM status
        * @return	{Boolean}	true if the date is OOM
        */
        isDateOOM: function(date) {
            return (date.getMonth() != this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth());
        },

        /**
        * Determines whether a given date is OOB (out of bounds - less than the mindate or more than the maxdate).
        *
        * @method	isDateOOB
        * @param	{Date}	date	The JavaScript Date object for which to check the OOB status
        * @return	{Boolean}	true if the date is OOB
        */
        isDateOOB: function(date) {
            var minDate = this.cfg.getProperty(DEF_CFG.MINDATE.key),
			maxDate = this.cfg.getProperty(DEF_CFG.MAXDATE.key),
			dm = DateMath;

            if (minDate) {
                minDate = dm.clearTime(minDate);
            }
            if (maxDate) {
                maxDate = dm.clearTime(maxDate);
            }

            var clearedDate = new Date(date.getTime());
            clearedDate = dm.clearTime(clearedDate);

            return ((minDate && clearedDate.getTime() < minDate.getTime()) || (maxDate && clearedDate.getTime() > maxDate.getTime()));
        },

        /**
        * Parses a pagedate configuration property value. The value can either be specified as a string of form "mm/yyyy" or a Date object 
        * and is parsed into a Date object normalized to the first day of the month. If no value is passed in, the month and year from today's date are used to create the Date object 
        * @method	_parsePageDate
        * @private
        * @param {Date|String}	date	Pagedate value which needs to be parsed
        * @return {Date}	The Date object representing the pagedate
        */
        _parsePageDate: function(date) {
            var parsedDate;

            if (date) {
                if (date instanceof Date) {
                    parsedDate = DateMath.findMonthStart(date);
                } else {
                    var month, year, aMonthYear;
                    aMonthYear = date.split(this.cfg.getProperty(DEF_CFG.DATE_FIELD_DELIMITER.key));
                    month = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_MONTH_POSITION.key) - 1], 10) - 1;
                    year = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_YEAR_POSITION.key) - 1], 10);

                    parsedDate = DateMath.getDate(year, month, 1);
                }
            } else {
                parsedDate = DateMath.getDate(this.today.getFullYear(), this.today.getMonth(), 1);
            }
            return parsedDate;
        },

        // END UTILITY METHODS

        // BEGIN EVENT HANDLERS

        /**
        * Event executed before a date is selected in the calendar widget.
        * @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent.
        */
        onBeforeSelect: function() {
            if (this.cfg.getProperty(DEF_CFG.MULTI_SELECT.key) === false) {
                if (this.parent) {
                    this.parent.callChildFunction("clearAllBodyCellStyles", this.Style.CSS_CELL_SELECTED);
                    this.parent.deselectAll();
                } else {
                    this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);
                    this.deselectAll();
                }
            }
        },

        /**
        * Event executed when a date is selected in the calendar widget.
        * @param	{Array}	selected	An array of date field arrays representing which date or dates were selected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
        * @deprecated Event handlers for this event should be susbcribed to selectEvent.
        */
        onSelect: function(selected) { },

        /**
        * Event executed before a date is deselected in the calendar widget.
        * @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent.
        */
        onBeforeDeselect: function() { },

        /**
        * Event executed when a date is deselected in the calendar widget.
        * @param	{Array}	selected	An array of date field arrays representing which date or dates were deselected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
        * @deprecated Event handlers for this event should be susbcribed to deselectEvent.
        */
        onDeselect: function(deselected) { },

        /**
        * Event executed when the user navigates to a different calendar page.
        * @deprecated Event handlers for this event should be susbcribed to changePageEvent.
        */
        onChangePage: function() {
            this.render();
        },

        /**
        * Event executed when the calendar widget is rendered.
        * @deprecated Event handlers for this event should be susbcribed to renderEvent.
        */
        onRender: function() { },

        /**
        * Event executed when the calendar widget is reset to its original state.
        * @deprecated Event handlers for this event should be susbcribed to resetEvemt.
        */
        onReset: function() { this.render(); },

        /**
        * Event executed when the calendar widget is completely cleared to the current month with no selections.
        * @deprecated Event handlers for this event should be susbcribed to clearEvent.
        */
        onClear: function() { this.render(); },

        /**
        * Validates the calendar widget. This method has no default implementation
        * and must be extended by subclassing the widget.
        * @return	Should return true if the widget validates, and false if
        * it doesn't.
        * @type Boolean
        */
        validate: function() { return true; },

        // END EVENT HANDLERS

        // BEGIN DATE PARSE METHODS

        /**
        * Converts a date string to a date field array
        * @private
        * @param	{String}	sDate			Date string. Valid formats are mm/dd and mm/dd/yyyy.
        * @return				A date field array representing the string passed to the method
        * @type Array[](Number[])
        */
        _parseDate: function(sDate) {
            var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER),
			rArray;

            if (aDate.length == 2) {
                rArray = [aDate[this.Locale.MD_MONTH_POSITION - 1], aDate[this.Locale.MD_DAY_POSITION - 1]];
                rArray.type = Calendar.MONTH_DAY;
            } else {
                rArray = [aDate[this.Locale.MDY_YEAR_POSITION - 1], aDate[this.Locale.MDY_MONTH_POSITION - 1], aDate[this.Locale.MDY_DAY_POSITION - 1]];
                rArray.type = Calendar.DATE;
            }

            for (var i = 0; i < rArray.length; i++) {
                rArray[i] = parseInt(rArray[i], 10);
            }

            return rArray;
        },

        /**
        * Converts a multi or single-date string to an array of date field arrays
        * @private
        * @param	{String}	sDates		Date string with one or more comma-delimited dates. Valid formats are mm/dd, mm/dd/yyyy, mm/dd/yyyy-mm/dd/yyyy
        * @return							An array of date field arrays
        * @type Array[](Number[])
        */
        _parseDates: function(sDates) {
            var aReturn = [],
			aDates = sDates.split(this.Locale.DATE_DELIMITER);

            for (var d = 0; d < aDates.length; ++d) {
                var sDate = aDates[d];

                if (sDate.indexOf(this.Locale.DATE_RANGE_DELIMITER) != -1) {
                    // This is a range
                    var aRange = sDate.split(this.Locale.DATE_RANGE_DELIMITER),
					dateStart = this._parseDate(aRange[0]),
					dateEnd = this._parseDate(aRange[1]),
					fullRange = this._parseRange(dateStart, dateEnd);

                    aReturn = aReturn.concat(fullRange);
                } else {
                    // This is not a range
                    var aDate = this._parseDate(sDate);
                    aReturn.push(aDate);
                }
            }
            return aReturn;
        },

        /**
        * Converts a date range to the full list of included dates
        * @private
        * @param	{Number[]}	startDate	Date field array representing the first date in the range
        * @param	{Number[]}	endDate		Date field array representing the last date in the range
        * @return							An array of date field arrays
        * @type Array[](Number[])
        */
        _parseRange: function(startDate, endDate) {
            var dCurrent = DateMath.add(DateMath.getDate(startDate[0], startDate[1] - 1, startDate[2]), DateMath.DAY, 1),
			dEnd = DateMath.getDate(endDate[0], endDate[1] - 1, endDate[2]),
			results = [];

            results.push(startDate);
            while (dCurrent.getTime() <= dEnd.getTime()) {
                results.push([dCurrent.getFullYear(), dCurrent.getMonth() + 1, dCurrent.getDate()]);
                dCurrent = DateMath.add(dCurrent, DateMath.DAY, 1);
            }
            return results;
        },

        // END DATE PARSE METHODS

        // BEGIN RENDERER METHODS

        /**
        * Resets the render stack of the current calendar to its original pre-render value.
        */
        resetRenderers: function() {
            this.renderStack = this._renderStack.concat();
        },

        /**
        * Removes all custom renderers added to the Calendar through the addRenderer, addMonthRenderer and 
        * addWeekdayRenderer methods. Calendar's render method needs to be called after removing renderers 
        * to re-render the Calendar without custom renderers applied.
        */
        removeRenderers: function() {
            this._renderStack = [];
            this.renderStack = [];
        },

        /**
        * Clears the inner HTML, CSS class and style information from the specified cell.
        * @method clearElement
        * @param	{HTMLTableCellElement} cell The cell to clear
        */
        clearElement: function(cell) {
            cell.innerHTML = "&#160;";
            cell.className = "";
        },

        /**
        * Adds a renderer to the render stack. The function reference passed to this method will be executed
        * when a date cell matches the conditions specified in the date string for this renderer.
        * @method addRenderer
        * @param	{String}	sDates		A date string to associate with the specified renderer. Valid formats
        *									include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        addRenderer: function(sDates, fnRender) {
            var aDates = this._parseDates(sDates);
            for (var i = 0; i < aDates.length; ++i) {
                var aDate = aDates[i];

                if (aDate.length == 2) { // this is either a range or a month/day combo
                    if (aDate[0] instanceof Array) { // this is a range
                        this._addRenderer(Calendar.RANGE, aDate, fnRender);
                    } else { // this is a month/day combo
                        this._addRenderer(Calendar.MONTH_DAY, aDate, fnRender);
                    }
                } else if (aDate.length == 3) {
                    this._addRenderer(Calendar.DATE, aDate, fnRender);
                }
            }
        },

        /**
        * The private method used for adding cell renderers to the local render stack.
        * This method is called by other methods that set the renderer type prior to the method call.
        * @method _addRenderer
        * @private
        * @param	{String}	type		The type string that indicates the type of date renderer being added.
        *									Values are YAHOO.widget.Calendar.DATE, YAHOO.widget.Calendar.MONTH_DAY, YAHOO.widget.Calendar.WEEKDAY,
        *									YAHOO.widget.Calendar.RANGE, YAHOO.widget.Calendar.MONTH
        * @param	{Array}		aDates		An array of dates used to construct the renderer. The format varies based
        *									on the renderer type
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        _addRenderer: function(type, aDates, fnRender) {
            var add = [type, aDates, fnRender];
            this.renderStack.unshift(add);
            this._renderStack = this.renderStack.concat();
        },

        /**
        * Adds a month to the render stack. The function reference passed to this method will be executed
        * when a date cell matches the month passed to this method.
        * @method addMonthRenderer
        * @param	{Number}	month		The month (1-12) to associate with this renderer
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        addMonthRenderer: function(month, fnRender) {
            this._addRenderer(Calendar.MONTH, [month], fnRender);
        },

        /**
        * Adds a weekday to the render stack. The function reference passed to this method will be executed
        * when a date cell matches the weekday passed to this method.
        * @method addWeekdayRenderer
        * @param	{Number}	weekday		The weekday (Sunday = 1, Monday = 2 ... Saturday = 7) to associate with this renderer
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        addWeekdayRenderer: function(weekday, fnRender) {
            this._addRenderer(Calendar.WEEKDAY, [weekday], fnRender);
        },

        // END RENDERER METHODS

        // BEGIN CSS METHODS

        /**
        * Removes all styles from all body cells in the current calendar table.
        * @method clearAllBodyCellStyles
        * @param	{style}	style The CSS class name to remove from all calendar body cells
        */
        clearAllBodyCellStyles: function(style) {
            for (var c = 0; c < this.cells.length; ++c) {
                Dom.removeClass(this.cells[c], style);
            }
        },

        // END CSS METHODS

        // BEGIN GETTER/SETTER METHODS
        /**
        * Sets the calendar's month explicitly
        * @method setMonth
        * @param {Number}	month		The numeric month, from 0 (January) to 11 (December)
        */
        setMonth: function(month) {
            var cfgPageDate = DEF_CFG.PAGEDATE.key,
			current = this.cfg.getProperty(cfgPageDate);
            current.setMonth(parseInt(month, 10));
            this.cfg.setProperty(cfgPageDate, current);
        },

        /**
        * Sets the calendar's year explicitly.
        * @method setYear
        * @param {Number}	year		The numeric 4-digit year
        */
        setYear: function(year) {
            var cfgPageDate = DEF_CFG.PAGEDATE.key,
			current = this.cfg.getProperty(cfgPageDate);

            current.setFullYear(parseInt(year, 10));
            this.cfg.setProperty(cfgPageDate, current);
        },

        /**
        * Gets the list of currently selected dates from the calendar.
        * @method getSelectedDates
        * @return {Date[]} An array of currently selected JavaScript Date objects.
        */
        getSelectedDates: function() {
            var returnDates = [],
			selected = this.cfg.getProperty(DEF_CFG.SELECTED.key);

            for (var d = 0; d < selected.length; ++d) {
                var dateArray = selected[d];

                var date = DateMath.getDate(dateArray[0], dateArray[1] - 1, dateArray[2]);
                //pwj patch begin
                if (date != "NaN") {
                    ///below one line is original
                    returnDates.push(date);
                }
                //end patch
            }

            returnDates.sort(function(a, b) { return a - b; });
            return returnDates;
        },

        /// END GETTER/SETTER METHODS ///

        /**
        * Hides the Calendar's outer container from view.
        * @method hide
        */
        hide: function() {
            if (this.beforeHideEvent.fire()) {
                this.oDomContainer.style.display = "none";
                this.hideEvent.fire();
            }
        },

        /**
        * Shows the Calendar's outer container.
        * @method show
        */
        show: function() {
            if (this.beforeShowEvent.fire()) {
                this.oDomContainer.style.display = "block";
                this.showEvent.fire();
            }
        },

        /**
        * Returns a string representing the current browser.
        * @deprecated As of 2.3.0, environment information is available in YAHOO.env.ua
        * @see YAHOO.env.ua
        * @property browser
        * @type String
        */
        browser: (function() {
            var ua = navigator.userAgent.toLowerCase();
            if (ua.indexOf('opera') != -1) { // Opera (check first in case of spoof)
                return 'opera';
            } else if (ua.indexOf('msie 7') != -1) { // IE7
                return 'ie7';
            } else if (ua.indexOf('msie') != -1) { // IE
                return 'ie';
            } else if (ua.indexOf('safari') != -1) { // Safari (check before Gecko because it includes "like Gecko")
                return 'safari';
            } else if (ua.indexOf('gecko') != -1) { // Gecko
                return 'gecko';
            } else {
                return false;
            }
        })(),
        /**
        * Returns a string representation of the object.
        * @method toString
        * @return {String}	A string representation of the Calendar object.
        */
        toString: function() {
            return "Calendar " + this.id;
        },

        /**
        * Destroys the Calendar instance. The method will remove references
        * to HTML elements, remove any event listeners added by the Calendar,
        * and destroy the Config and CalendarNavigator instances it has created.
        *
        * @method destroy
        */
        destroy: function() {

            if (this.beforeDestroyEvent.fire()) {
                var cal = this;

                // Child objects
                if (cal.navigator) {
                    cal.navigator.destroy();
                }

                if (cal.cfg) {
                    cal.cfg.destroy();
                }

                // DOM event listeners
                Event.purgeElement(cal.oDomContainer, true);

                // Generated markup/DOM - Not removing the container DIV since we didn't create it.
                Dom.removeClass(cal.oDomContainer, "withtitle");
                Dom.removeClass(cal.oDomContainer, cal.Style.CSS_CONTAINER);
                Dom.removeClass(cal.oDomContainer, cal.Style.CSS_SINGLE);
                cal.oDomContainer.innerHTML = "";

                // JS-to-DOM references
                cal.oDomContainer = null;
                cal.cells = null;

                this.destroyEvent.fire();
            }
        }
    };

    YAHOO.widget.Calendar = Calendar;

    /**
    * @namespace YAHOO.widget
    * @class Calendar_Core
    * @extends YAHOO.widget.Calendar
    * @deprecated The old Calendar_Core class is no longer necessary.
    */
    YAHOO.widget.Calendar_Core = YAHOO.widget.Calendar;

    YAHOO.widget.Cal_Core = YAHOO.widget.Calendar;

})();

(function() {

    var Dom = YAHOO.util.Dom,
		DateMath = YAHOO.widget.DateMath,
		Event = YAHOO.util.Event,
		Lang = YAHOO.lang,
		Calendar = YAHOO.widget.Calendar;

    /**
    * YAHOO.widget.CalendarGroup is a special container class for YAHOO.widget.Calendar. This class facilitates
    * the ability to have multi-page calendar views that share a single dataset and are
    * dependent on each other.
    *
    * The calendar group instance will refer to each of its elements using a 0-based index.
    * For example, to construct the placeholder for a calendar group widget with id "cal1" and
    * containerId of "cal1Container", the markup would be as follows:
    *	<xmp>
    *		<div id="cal1Container_0"></div>
    *		<div id="cal1Container_1"></div>
    *	</xmp>
    * The tables for the calendars ("cal1_0" and "cal1_1") will be inserted into those containers.
    * 
    * <p>
    * <strong>NOTE: As of 2.4.0, the constructor's ID argument is optional.</strong>
    * The CalendarGroup can be constructed by simply providing a container ID string, 
    * or a reference to a container DIV HTMLElement (the element needs to exist 
    * in the document).
    * 
    * E.g.:
    *	<xmp>
    *		var c = new YAHOO.widget.CalendarGroup("calContainer", configOptions);
    *	</xmp>
    * or:
    *   <xmp>
    *       var containerDiv = YAHOO.util.Dom.get("calContainer");
    *		var c = new YAHOO.widget.CalendarGroup(containerDiv, configOptions);
    *	</xmp>
    * </p>
    * <p>
    * If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
    * For example if an ID is not provided, and the container's ID is "calContainer", the CalendarGroup's ID will be set to "calContainer_t".
    * </p>
    * 
    * @namespace YAHOO.widget
    * @class CalendarGroup
    * @constructor
    * @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
    * @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
    * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
    */
    function CalendarGroup(id, containerId, config) {
        if (arguments.length > 0) {
            this.init.apply(this, arguments);
        }
    }

    /**
    * The set of default Config property keys and values for the CalendarGroup
    * @property YAHOO.widget.CalendarGroup._DEFAULT_CONFIG
    * @final
    * @static
    * @private
    * @type Object
    */
    CalendarGroup._DEFAULT_CONFIG = Calendar._DEFAULT_CONFIG;
    CalendarGroup._DEFAULT_CONFIG.PAGES = { key: "pages", value: 2 };

    var DEF_CFG = CalendarGroup._DEFAULT_CONFIG;

    CalendarGroup.prototype = {

        /**
        * Initializes the calendar group. All subclasses must call this method in order for the
        * group to be initialized properly.
        * @method init
        * @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
        * @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
        * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
        */
        init: function(id, container, config) {

            // Normalize 2.4.0, pre 2.4.0 args
            var nArgs = this._parseArgs(arguments);

            id = nArgs.id;
            container = nArgs.container;
            config = nArgs.config;

            this.oDomContainer = Dom.get(container);

            if (!this.oDomContainer.id) {
                this.oDomContainer.id = Dom.generateId();
            }
            if (!id) {
                id = this.oDomContainer.id + "_t";
            }

            /**
            * The unique id associated with the CalendarGroup
            * @property id
            * @type String
            */
            this.id = id;

            /**
            * The unique id associated with the CalendarGroup container
            * @property containerId
            * @type String
            */
            this.containerId = this.oDomContainer.id;

            this.initEvents();
            this.initStyles();

            /**
            * The collection of Calendar pages contained within the CalendarGroup
            * @property pages
            * @type YAHOO.widget.Calendar[]
            */
            this.pages = [];

            Dom.addClass(this.oDomContainer, CalendarGroup.CSS_CONTAINER);
            Dom.addClass(this.oDomContainer, CalendarGroup.CSS_MULTI_UP);

            /**
            * The Config object used to hold the configuration variables for the CalendarGroup
            * @property cfg
            * @type YAHOO.util.Config
            */
            this.cfg = new YAHOO.util.Config(this);

            /**
            * The local object which contains the CalendarGroup's options
            * @property Options
            * @type Object
            */
            this.Options = {};

            /**
            * The local object which contains the CalendarGroup's locale settings
            * @property Locale
            * @type Object
            */
            this.Locale = {};

            this.setupConfig();

            if (config) {
                this.cfg.applyConfig(config, true);
            }

            this.cfg.fireQueue();

            // OPERA HACK FOR MISWRAPPED FLOATS
            if (YAHOO.env.ua.opera) {
                this.renderEvent.subscribe(this._fixWidth, this, true);
                this.showEvent.subscribe(this._fixWidth, this, true);
            }

        },

        setupConfig: function() {

            var cfg = this.cfg;

            /**
            * The number of pages to include in the CalendarGroup. This value can only be set once, in the CalendarGroup's constructor arguments.
            * @config pages
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.PAGES.key, { value: DEF_CFG.PAGES.value, validator: cfg.checkNumber, handler: this.configPages });

            /**
            * The month/year representing the current visible Calendar date (mm/yyyy)
            * @config pagedate
            * @type String | Date
            * @default today's date
            */
            cfg.addProperty(DEF_CFG.PAGEDATE.key, { value: new Date(), handler: this.configPageDate });

            /**
            * The date or range of dates representing the current Calendar selection
            *
            * @config selected
            * @type String
            * @default []
            */
            cfg.addProperty(DEF_CFG.SELECTED.key, { value: [], handler: this.configSelected });

            /**
            * The title to display above the CalendarGroup's month header
            * @config title
            * @type String
            * @default ""
            */
            cfg.addProperty(DEF_CFG.TITLE.key, { value: DEF_CFG.TITLE.value, handler: this.configTitle });

            /**
            * Whether or not a close button should be displayed for this CalendarGroup
            * @config close
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.CLOSE.key, { value: DEF_CFG.CLOSE.value, handler: this.configClose });

            /**
            * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
            * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be 
            * enabled if required.
            * 
            * @config iframe
            * @type Boolean
            * @default true for IE6 and below, false for all other browsers
            */
            cfg.addProperty(DEF_CFG.IFRAME.key, { value: DEF_CFG.IFRAME.value, handler: this.configIframe, validator: cfg.checkBoolean });

            /**
            * The minimum selectable date in the current Calendar (mm/dd/yyyy)
            * @config mindate
            * @type String | Date
            * @default null
            */
            cfg.addProperty(DEF_CFG.MINDATE.key, { value: DEF_CFG.MINDATE.value, handler: this.delegateConfig });

            /**
            * The maximum selectable date in the current Calendar (mm/dd/yyyy)
            * @config maxdate
            * @type String | Date
            * @default null
            */
            cfg.addProperty(DEF_CFG.MAXDATE.key, { value: DEF_CFG.MAXDATE.value, handler: this.delegateConfig });

            // Options properties

            /**
            * True if the Calendar should allow multiple selections. False by default.
            * @config MULTI_SELECT
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.MULTI_SELECT.key, { value: DEF_CFG.MULTI_SELECT.value, handler: this.delegateConfig, validator: cfg.checkBoolean });

            /**
            * The weekday the week begins on. Default is 0 (Sunday).
            * @config START_WEEKDAY
            * @type number
            * @default 0
            */
            cfg.addProperty(DEF_CFG.START_WEEKDAY.key, { value: DEF_CFG.START_WEEKDAY.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * True if the Calendar should show weekday labels. True by default.
            * @config SHOW_WEEKDAYS
            * @type Boolean
            * @default true
            */
            cfg.addProperty(DEF_CFG.SHOW_WEEKDAYS.key, { value: DEF_CFG.SHOW_WEEKDAYS.value, handler: this.delegateConfig, validator: cfg.checkBoolean });

            /**
            * True if the Calendar should show week row headers. False by default.
            * @config SHOW_WEEK_HEADER
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.SHOW_WEEK_HEADER.key, { value: DEF_CFG.SHOW_WEEK_HEADER.value, handler: this.delegateConfig, validator: cfg.checkBoolean });

            /**
            * True if the Calendar should show week row footers. False by default.
            * @config SHOW_WEEK_FOOTER
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.SHOW_WEEK_FOOTER.key, { value: DEF_CFG.SHOW_WEEK_FOOTER.value, handler: this.delegateConfig, validator: cfg.checkBoolean });

            /**
            * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
            * @config HIDE_BLANK_WEEKS
            * @type Boolean
            * @default false
            */
            cfg.addProperty(DEF_CFG.HIDE_BLANK_WEEKS.key, { value: DEF_CFG.HIDE_BLANK_WEEKS.value, handler: this.delegateConfig, validator: cfg.checkBoolean });

            /**
            * The image that should be used for the left navigation arrow.
            * @config NAV_ARROW_LEFT
            * @type String
            * @deprecated	You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
            * @default null
            */
            cfg.addProperty(DEF_CFG.NAV_ARROW_LEFT.key, { value: DEF_CFG.NAV_ARROW_LEFT.value, handler: this.delegateConfig });

            /**
            * The image that should be used for the right navigation arrow.
            * @config NAV_ARROW_RIGHT
            * @type String
            * @deprecated	You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
            * @default null
            */
            cfg.addProperty(DEF_CFG.NAV_ARROW_RIGHT.key, { value: DEF_CFG.NAV_ARROW_RIGHT.value, handler: this.delegateConfig });

            // Locale properties

            /**
            * The short month labels for the current locale.
            * @config MONTHS_SHORT
            * @type String[]
            * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
            */
            cfg.addProperty(DEF_CFG.MONTHS_SHORT.key, { value: DEF_CFG.MONTHS_SHORT.value, handler: this.delegateConfig });

            /**
            * The long month labels for the current locale.
            * @config MONTHS_LONG
            * @type String[]
            * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
            */
            cfg.addProperty(DEF_CFG.MONTHS_LONG.key, { value: DEF_CFG.MONTHS_LONG.value, handler: this.delegateConfig });

            /**
            * The 1-character weekday labels for the current locale.
            * @config WEEKDAYS_1CHAR
            * @type String[]
            * @default ["S", "M", "T", "W", "T", "F", "S"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_1CHAR.key, { value: DEF_CFG.WEEKDAYS_1CHAR.value, handler: this.delegateConfig });

            /**
            * The short weekday labels for the current locale.
            * @config WEEKDAYS_SHORT
            * @type String[]
            * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_SHORT.key, { value: DEF_CFG.WEEKDAYS_SHORT.value, handler: this.delegateConfig });

            /**
            * The medium weekday labels for the current locale.
            * @config WEEKDAYS_MEDIUM
            * @type String[]
            * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_MEDIUM.key, { value: DEF_CFG.WEEKDAYS_MEDIUM.value, handler: this.delegateConfig });

            /**
            * The long weekday labels for the current locale.
            * @config WEEKDAYS_LONG
            * @type String[]
            * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
            */
            cfg.addProperty(DEF_CFG.WEEKDAYS_LONG.key, { value: DEF_CFG.WEEKDAYS_LONG.value, handler: this.delegateConfig });

            /**
            * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
            * @config LOCALE_MONTHS
            * @type String
            * @default "long"
            */
            cfg.addProperty(DEF_CFG.LOCALE_MONTHS.key, { value: DEF_CFG.LOCALE_MONTHS.value, handler: this.delegateConfig });

            /**
            * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
            * @config LOCALE_WEEKDAYS
            * @type String
            * @default "short"
            */
            cfg.addProperty(DEF_CFG.LOCALE_WEEKDAYS.key, { value: DEF_CFG.LOCALE_WEEKDAYS.value, handler: this.delegateConfig });

            /**
            * The value used to delimit individual dates in a date string passed to various Calendar functions.
            * @config DATE_DELIMITER
            * @type String
            * @default ","
            */
            cfg.addProperty(DEF_CFG.DATE_DELIMITER.key, { value: DEF_CFG.DATE_DELIMITER.value, handler: this.delegateConfig });

            /**
            * The value used to delimit date fields in a date string passed to various Calendar functions.
            * @config DATE_FIELD_DELIMITER
            * @type String
            * @default "/"
            */
            cfg.addProperty(DEF_CFG.DATE_FIELD_DELIMITER.key, { value: DEF_CFG.DATE_FIELD_DELIMITER.value, handler: this.delegateConfig });

            /**
            * The value used to delimit date ranges in a date string passed to various Calendar functions.
            * @config DATE_RANGE_DELIMITER
            * @type String
            * @default "-"
            */
            cfg.addProperty(DEF_CFG.DATE_RANGE_DELIMITER.key, { value: DEF_CFG.DATE_RANGE_DELIMITER.value, handler: this.delegateConfig });

            /**
            * The position of the month in a month/year date string
            * @config MY_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MY_MONTH_POSITION.key, { value: DEF_CFG.MY_MONTH_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the year in a month/year date string
            * @config MY_YEAR_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MY_YEAR_POSITION.key, { value: DEF_CFG.MY_YEAR_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the month in a month/day date string
            * @config MD_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MD_MONTH_POSITION.key, { value: DEF_CFG.MD_MONTH_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the day in a month/year date string
            * @config MD_DAY_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MD_DAY_POSITION.key, { value: DEF_CFG.MD_DAY_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the month in a month/day/year date string
            * @config MDY_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MDY_MONTH_POSITION.key, { value: DEF_CFG.MDY_MONTH_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the day in a month/day/year date string
            * @config MDY_DAY_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MDY_DAY_POSITION.key, { value: DEF_CFG.MDY_DAY_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the year in a month/day/year date string
            * @config MDY_YEAR_POSITION
            * @type Number
            * @default 3
            */
            cfg.addProperty(DEF_CFG.MDY_YEAR_POSITION.key, { value: DEF_CFG.MDY_YEAR_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the month in the month year label string used as the Calendar header
            * @config MY_LABEL_MONTH_POSITION
            * @type Number
            * @default 1
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_POSITION.key, { value: DEF_CFG.MY_LABEL_MONTH_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The position of the year in the month year label string used as the Calendar header
            * @config MY_LABEL_YEAR_POSITION
            * @type Number
            * @default 2
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_POSITION.key, { value: DEF_CFG.MY_LABEL_YEAR_POSITION.value, handler: this.delegateConfig, validator: cfg.checkNumber });

            /**
            * The suffix used after the month when rendering the Calendar header
            * @config MY_LABEL_MONTH_SUFFIX
            * @type String
            * @default " "
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_SUFFIX.key, { value: DEF_CFG.MY_LABEL_MONTH_SUFFIX.value, handler: this.delegateConfig });

            /**
            * The suffix used after the year when rendering the Calendar header
            * @config MY_LABEL_YEAR_SUFFIX
            * @type String
            * @default ""
            */
            cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_SUFFIX.key, { value: DEF_CFG.MY_LABEL_YEAR_SUFFIX.value, handler: this.delegateConfig });

            /**
            * Configuration for the Month Year Navigation UI. By default it is disabled
            * @config NAV
            * @type Object
            * @default null
            */
            cfg.addProperty(DEF_CFG.NAV.key, { value: DEF_CFG.NAV.value, handler: this.configNavigator });

            /**
            * The map of UI strings which the CalendarGroup UI uses.
            *
            * @config strings
            * @type {Object}
            * @default An object with the properties shown below:
            *     <dl>
            *         <dt>previousMonth</dt><dd><em>String</em> : The string to use for the "Previous Month" navigation UI. Defaults to "Previous Month".</dd>
            *         <dt>nextMonth</dt><dd><em>String</em> : The string to use for the "Next Month" navigation UI. Defaults to "Next Month".</dd>
            *         <dt>close</dt><dd><em>String</em> : The string to use for the close button label. Defaults to "Close".</dd>
            *     </dl>
            */
            cfg.addProperty(DEF_CFG.STRINGS.key, {
                value: DEF_CFG.STRINGS.value,
                handler: this.configStrings,
                validator: function(val) {
                    return Lang.isObject(val);
                },
                supercedes: DEF_CFG.STRINGS.supercedes
            });
        },

        /**
        * Initializes CalendarGroup's built-in CustomEvents
        * @method initEvents
        */
        initEvents: function() {

            var me = this,
			strEvent = "Event",
			CE = YAHOO.util.CustomEvent;

            /**
            * Proxy subscriber to subscribe to the CalendarGroup's child Calendars' CustomEvents
            * @method sub
            * @private
            * @param {Function} fn	The function to subscribe to this CustomEvent
            * @param {Object}	obj	The CustomEvent's scope object
            * @param {Boolean}	bOverride	Whether or not to apply scope correction
            */
            var sub = function(fn, obj, bOverride) {
                for (var p = 0; p < me.pages.length; ++p) {
                    var cal = me.pages[p];
                    cal[this.type + strEvent].subscribe(fn, obj, bOverride);
                }
            };

            /**
            * Proxy unsubscriber to unsubscribe from the CalendarGroup's child Calendars' CustomEvents
            * @method unsub
            * @private
            * @param {Function} fn	The function to subscribe to this CustomEvent
            * @param {Object}	obj	The CustomEvent's scope object
            */
            var unsub = function(fn, obj) {
                for (var p = 0; p < me.pages.length; ++p) {
                    var cal = me.pages[p];
                    cal[this.type + strEvent].unsubscribe(fn, obj);
                }
            };

            var defEvents = Calendar._EVENT_TYPES;

            /**
            * Fired before a selection is made
            * @event beforeSelectEvent
            */
            me.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT);
            me.beforeSelectEvent.subscribe = sub; me.beforeSelectEvent.unsubscribe = unsub;

            /**
            * Fired when a selection is made
            * @event selectEvent
            * @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
            */
            me.selectEvent = new CE(defEvents.SELECT);
            me.selectEvent.subscribe = sub; me.selectEvent.unsubscribe = unsub;

            /**
            * Fired before a selection is made
            * @event beforeDeselectEvent
            */
            me.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT);
            me.beforeDeselectEvent.subscribe = sub; me.beforeDeselectEvent.unsubscribe = unsub;

            /**
            * Fired when a selection is made
            * @event deselectEvent
            * @param {Array}	Array of Date field arrays in the format [YYYY, MM, DD].
            */
            me.deselectEvent = new CE(defEvents.DESELECT);
            me.deselectEvent.subscribe = sub; me.deselectEvent.unsubscribe = unsub;

            /**
            * Fired when the Calendar page is changed
            * @event changePageEvent
            */
            me.changePageEvent = new CE(defEvents.CHANGE_PAGE);
            me.changePageEvent.subscribe = sub; me.changePageEvent.unsubscribe = unsub;

            /**
            * Fired before the Calendar is rendered
            * @event beforeRenderEvent
            */
            me.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER);
            me.beforeRenderEvent.subscribe = sub; me.beforeRenderEvent.unsubscribe = unsub;

            /**
            * Fired when the Calendar is rendered
            * @event renderEvent
            */
            me.renderEvent = new CE(defEvents.RENDER);
            me.renderEvent.subscribe = sub; me.renderEvent.unsubscribe = unsub;

            /**
            * Fired when the Calendar is reset
            * @event resetEvent
            */
            me.resetEvent = new CE(defEvents.RESET);
            me.resetEvent.subscribe = sub; me.resetEvent.unsubscribe = unsub;

            /**
            * Fired when the Calendar is cleared
            * @event clearEvent
            */
            me.clearEvent = new CE(defEvents.CLEAR);
            me.clearEvent.subscribe = sub; me.clearEvent.unsubscribe = unsub;

            /**
            * Fired just before the CalendarGroup is to be shown
            * @event beforeShowEvent
            */
            me.beforeShowEvent = new CE(defEvents.BEFORE_SHOW);

            /**
            * Fired after the CalendarGroup is shown
            * @event showEvent
            */
            me.showEvent = new CE(defEvents.SHOW);

            /**
            * Fired just before the CalendarGroup is to be hidden
            * @event beforeHideEvent
            */
            me.beforeHideEvent = new CE(defEvents.BEFORE_HIDE);

            /**
            * Fired after the CalendarGroup is hidden
            * @event hideEvent
            */
            me.hideEvent = new CE(defEvents.HIDE);

            /**
            * Fired just before the CalendarNavigator is to be shown
            * @event beforeShowNavEvent
            */
            me.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV);

            /**
            * Fired after the CalendarNavigator is shown
            * @event showNavEvent
            */
            me.showNavEvent = new CE(defEvents.SHOW_NAV);

            /**
            * Fired just before the CalendarNavigator is to be hidden
            * @event beforeHideNavEvent
            */
            me.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV);

            /**
            * Fired after the CalendarNavigator is hidden
            * @event hideNavEvent
            */
            me.hideNavEvent = new CE(defEvents.HIDE_NAV);

            /**
            * Fired just before the CalendarNavigator is to be rendered
            * @event beforeRenderNavEvent
            */
            me.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV);

            /**
            * Fired after the CalendarNavigator is rendered
            * @event renderNavEvent
            */
            me.renderNavEvent = new CE(defEvents.RENDER_NAV);

            /**
            * Fired just before the CalendarGroup is to be destroyed
            * @event beforeDestroyEvent
            */
            me.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY);

            /**
            * Fired after the CalendarGroup is destroyed. This event should be used
            * for notification only. When this event is fired, important CalendarGroup instance
            * properties, dom references and event listeners have already been 
            * removed/dereferenced, and hence the CalendarGroup instance is not in a usable 
            * state.
            *
            * @event destroyEvent
            */
            me.destroyEvent = new CE(defEvents.DESTROY);
        },

        /**
        * The default Config handler for the "pages" property
        * @method configPages
        * @param {String} type	The CustomEvent type (usually the property name)
        * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
        * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
        */
        configPages: function(type, args, obj) {
            var pageCount = args[0],
			cfgPageDate = DEF_CFG.PAGEDATE.key,
			sep = "_",
			groupCalClass = "groupcal",
			firstClass = "first-of-type",
			lastClass = "last-of-type";

            for (var p = 0; p < pageCount; ++p) {
                var calId = this.id + sep + p,
				calContainerId = this.containerId + sep + p,
				childConfig = this.cfg.getConfig();

                childConfig.close = false;
                childConfig.title = false;
                childConfig.navigator = null;

                var cal = this.constructChild(calId, calContainerId, childConfig);
                var caldate = cal.cfg.getProperty(cfgPageDate);
                this._setMonthOnDate(caldate, caldate.getMonth() + p);
                cal.cfg.setProperty(cfgPageDate, caldate);

                Dom.removeClass(cal.oDomContainer, this.Style.CSS_SINGLE);
                Dom.addClass(cal.oDomContainer, groupCalClass);

                if (p === 0) {
                    Dom.addClass(cal.oDomContainer, firstClass);
                }

                if (p == (pageCount - 1)) {
                    Dom.addClass(cal.oDomContainer, lastClass);
                }

                cal.parent = this;
                cal.index = p;

                this.pages[this.pages.length] = cal;
            }
        },

        /**
        * The default Config handler for the "pagedate" property
        * @method configPageDate
        * @param {String} type	The CustomEvent type (usually the property name)
        * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
        * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
        */
        configPageDate: function(type, args, obj) {
            var val = args[0],
			firstPageDate;

            var cfgPageDate = DEF_CFG.PAGEDATE.key;

            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                if (p === 0) {
                    firstPageDate = cal._parsePageDate(val);
                    cal.cfg.setProperty(cfgPageDate, firstPageDate);
                } else {
                    var pageDate = new Date(firstPageDate);
                    this._setMonthOnDate(pageDate, pageDate.getMonth() + p);
                    cal.cfg.setProperty(cfgPageDate, pageDate);
                }
            }
        },

        /**
        * The default Config handler for the CalendarGroup "selected" property
        * @method configSelected
        * @param {String} type	The CustomEvent type (usually the property name)
        * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
        * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
        */
        configSelected: function(type, args, obj) {
            var cfgSelected = DEF_CFG.SELECTED.key;
            this.delegateConfig(type, args, obj);
            var selected = (this.pages.length > 0) ? this.pages[0].cfg.getProperty(cfgSelected) : [];
            this.cfg.setProperty(cfgSelected, selected, true);
        },


        /**
        * Delegates a configuration property to the CustomEvents associated with the CalendarGroup's children
        * @method delegateConfig
        * @param {String} type	The CustomEvent type (usually the property name)
        * @param {Object[]}	args	The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
        * @param {Object} obj	The scope object. For configuration handlers, this will usually equal the owner.
        */
        delegateConfig: function(type, args, obj) {
            var val = args[0];
            var cal;

            for (var p = 0; p < this.pages.length; p++) {
                cal = this.pages[p];
                cal.cfg.setProperty(type, val);
            }
        },

        /**
        * Adds a function to all child Calendars within this CalendarGroup.
        * @method setChildFunction
        * @param {String}		fnName		The name of the function
        * @param {Function}		fn			The function to apply to each Calendar page object
        */
        setChildFunction: function(fnName, fn) {
            var pageCount = this.cfg.getProperty(DEF_CFG.PAGES.key);

            for (var p = 0; p < pageCount; ++p) {
                this.pages[p][fnName] = fn;
            }
        },

        /**
        * Calls a function within all child Calendars within this CalendarGroup.
        * @method callChildFunction
        * @param {String}		fnName		The name of the function
        * @param {Array}		args		The arguments to pass to the function
        */
        callChildFunction: function(fnName, args) {
            var pageCount = this.cfg.getProperty(DEF_CFG.PAGES.key);

            for (var p = 0; p < pageCount; ++p) {
                var page = this.pages[p];
                if (page[fnName]) {
                    var fn = page[fnName];
                    fn.call(page, args);
                }
            }
        },

        /**
        * Constructs a child calendar. This method can be overridden if a subclassed version of the default
        * calendar is to be used.
        * @method constructChild
        * @param {String}	id			The id of the table element that will represent the calendar widget
        * @param {String}	containerId	The id of the container div element that will wrap the calendar table
        * @param {Object}	config		The configuration object containing the Calendar's arguments
        * @return {YAHOO.widget.Calendar}	The YAHOO.widget.Calendar instance that is constructed
        */
        constructChild: function(id, containerId, config) {
            var container = document.getElementById(containerId);
            if (!container) {
                container = document.createElement("div");
                container.id = containerId;
                this.oDomContainer.appendChild(container);
            }
            return new Calendar(id, containerId, config);
        },

        /**
        * Sets the calendar group's month explicitly. This month will be set into the first
        * page of the multi-page calendar, and all other months will be iterated appropriately.
        * @method setMonth
        * @param {Number}	month		The numeric month, from 0 (January) to 11 (December)
        */
        setMonth: function(month) {
            month = parseInt(month, 10);
            var currYear;

            var cfgPageDate = DEF_CFG.PAGEDATE.key;

            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                var pageDate = cal.cfg.getProperty(cfgPageDate);
                if (p === 0) {
                    currYear = pageDate.getFullYear();
                } else {
                    pageDate.setFullYear(currYear);
                }
                this._setMonthOnDate(pageDate, month + p);
                cal.cfg.setProperty(cfgPageDate, pageDate);
            }
        },

        /**
        * Sets the calendar group's year explicitly. This year will be set into the first
        * page of the multi-page calendar, and all other months will be iterated appropriately.
        * @method setYear
        * @param {Number}	year		The numeric 4-digit year
        */
        setYear: function(year) {

            var cfgPageDate = DEF_CFG.PAGEDATE.key;

            year = parseInt(year, 10);
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                var pageDate = cal.cfg.getProperty(cfgPageDate);

                if ((pageDate.getMonth() + 1) == 1 && p > 0) {
                    year += 1;
                }
                cal.setYear(year);
            }
        },

        /**
        * Calls the render function of all child calendars within the group.
        * @method render
        */
        render: function() {
            this.renderHeader();
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.render();
            }
            this.renderFooter();
        },

        /**
        * Selects a date or a collection of dates on the current calendar. This method, by default,
        * does not call the render method explicitly. Once selection has completed, render must be 
        * called for the changes to be reflected visually.
        * @method select
        * @param	{String/Date/Date[]}	date	The date string of dates to select in the current calendar. Valid formats are
        *								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
        *								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
        *								This method can also take a JavaScript Date object or an array of Date objects.
        * @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        select: function(date) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.select(date);
            }
            return this.getSelectedDates();
        },

        /**
        * Selects dates in the CalendarGroup based on the cell index provided. This method is used to select cells without having to do a full render. The selected style is applied to the cells directly.
        * The value of the MULTI_SELECT Configuration attribute will determine the set of dates which get selected. 
        * <ul>
        *    <li>If MULTI_SELECT is false, selectCell will select the cell at the specified index for only the last displayed Calendar page.</li>
        *    <li>If MULTI_SELECT is true, selectCell will select the cell at the specified index, on each displayed Calendar page.</li>
        * </ul>
        * @method selectCell
        * @param	{Number}	cellIndex	The index of the cell to be selected. 
        * @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        selectCell: function(cellIndex) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.selectCell(cellIndex);
            }
            return this.getSelectedDates();
        },

        /**
        * Deselects a date or a collection of dates on the current calendar. This method, by default,
        * does not call the render method explicitly. Once deselection has completed, render must be 
        * called for the changes to be reflected visually.
        * @method deselect
        * @param	{String/Date/Date[]}	date	The date string of dates to deselect in the current calendar. Valid formats are
        *								individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
        *								Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
        *								This method can also take a JavaScript Date object or an array of Date objects.	
        * @return	{Date[]}			Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        deselect: function(date) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.deselect(date);
            }
            return this.getSelectedDates();
        },

        /**
        * Deselects all dates on the current calendar.
        * @method deselectAll
        * @return {Date[]}		Array of JavaScript Date objects representing all individual dates that are currently selected.
        *						Assuming that this function executes properly, the return value should be an empty array.
        *						However, the empty array is returned for the sake of being able to check the selection status
        *						of the calendar.
        */
        deselectAll: function() {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.deselectAll();
            }
            return this.getSelectedDates();
        },

        /**
        * Deselects dates in the CalendarGroup based on the cell index provided. This method is used to select cells without having to do a full render. The selected style is applied to the cells directly.
        * deselectCell will deselect the cell at the specified index on each displayed Calendar page.
        *
        * @method deselectCell
        * @param	{Number}	cellIndex	The index of the cell to deselect. 
        * @return	{Date[]}	Array of JavaScript Date objects representing all individual dates that are currently selected.
        */
        deselectCell: function(cellIndex) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.deselectCell(cellIndex);
            }
            return this.getSelectedDates();
        },

        /**
        * Resets the calendar widget to the originally selected month and year, and 
        * sets the calendar to the initial selection(s).
        * @method reset
        */
        reset: function() {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.reset();
            }
        },

        /**
        * Clears the selected dates in the current calendar widget and sets the calendar
        * to the current month and year.
        * @method clear
        */
        clear: function() {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.clear();
            }

            this.cfg.setProperty(DEF_CFG.SELECTED.key, []);
            this.cfg.setProperty(DEF_CFG.PAGEDATE.key, new Date(this.pages[0].today.getTime()));
            this.render();
        },

        /**
        * Navigates to the next month page in the calendar widget.
        * @method nextMonth
        */
        nextMonth: function() {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.nextMonth();
            }
        },

        /**
        * Navigates to the previous month page in the calendar widget.
        * @method previousMonth
        */
        previousMonth: function() {
            for (var p = this.pages.length - 1; p >= 0; --p) {
                var cal = this.pages[p];
                cal.previousMonth();
            }
        },

        /**
        * Navigates to the next year in the currently selected month in the calendar widget.
        * @method nextYear
        */
        nextYear: function() {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.nextYear();
            }
        },

        /**
        * Navigates to the previous year in the currently selected month in the calendar widget.
        * @method previousYear
        */
        previousYear: function() {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.previousYear();
            }
        },

        /**
        * Gets the list of currently selected dates from the calendar.
        * @return			An array of currently selected JavaScript Date objects.
        * @type Date[]
        */
        getSelectedDates: function() {
            var returnDates = [];
            var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key);
            for (var d = 0; d < selected.length; ++d) {
                var dateArray = selected[d];

                var date = DateMath.getDate(dateArray[0], dateArray[1] - 1, dateArray[2]);
                // pwj patch
                if (date != "NaN") {
                    returnDates.push(date);
                }
            }

            returnDates.sort(function(a, b) { return a - b; });

            return returnDates;
        },

        /**
        * Adds a renderer to the render stack. The function reference passed to this method will be executed
        * when a date cell matches the conditions specified in the date string for this renderer.
        * @method addRenderer
        * @param	{String}	sDates		A date string to associate with the specified renderer. Valid formats
        *									include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        addRenderer: function(sDates, fnRender) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.addRenderer(sDates, fnRender);
            }
        },

        /**
        * Adds a month to the render stack. The function reference passed to this method will be executed
        * when a date cell matches the month passed to this method.
        * @method addMonthRenderer
        * @param	{Number}	month		The month (1-12) to associate with this renderer
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        addMonthRenderer: function(month, fnRender) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.addMonthRenderer(month, fnRender);
            }
        },

        /**
        * Adds a weekday to the render stack. The function reference passed to this method will be executed
        * when a date cell matches the weekday passed to this method.
        * @method addWeekdayRenderer
        * @param	{Number}	weekday		The weekday (1-7) to associate with this renderer. 1=Sunday, 2=Monday etc.
        * @param	{Function}	fnRender	The function executed to render cells that match the render rules for this renderer.
        */
        addWeekdayRenderer: function(weekday, fnRender) {
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                cal.addWeekdayRenderer(weekday, fnRender);
            }
        },

        /**
        * Removes all custom renderers added to the CalendarGroup through the addRenderer, addMonthRenderer and 
        * addWeekRenderer methods. CalendarGroup's render method needs to be called to after removing renderers 
        * to see the changes applied.
        * 
        * @method removeRenderers
        */
        removeRenderers: function() {
            this.callChildFunction("removeRenderers");
        },

        /**
        * Renders the header for the CalendarGroup.
        * @method renderHeader
        */
        renderHeader: function() {
            // EMPTY DEFAULT IMPL
        },

        /**
        * Renders a footer for the 2-up calendar container. By default, this method is
        * unimplemented.
        * @method renderFooter
        */
        renderFooter: function() {
            // EMPTY DEFAULT IMPL
        },

        /**
        * Adds the designated number of months to the current calendar month, and sets the current
        * calendar page date to the new month.
        * @method addMonths
        * @param {Number}	count	The number of months to add to the current calendar
        */
        addMonths: function(count) {
            this.callChildFunction("addMonths", count);
        },

        /**
        * Subtracts the designated number of months from the current calendar month, and sets the current
        * calendar page date to the new month.
        * @method subtractMonths
        * @param {Number}	count	The number of months to subtract from the current calendar
        */
        subtractMonths: function(count) {
            this.callChildFunction("subtractMonths", count);
        },

        /**
        * Adds the designated number of years to the current calendar, and sets the current
        * calendar page date to the new month.
        * @method addYears
        * @param {Number}	count	The number of years to add to the current calendar
        */
        addYears: function(count) {
            this.callChildFunction("addYears", count);
        },

        /**
        * Subtcats the designated number of years from the current calendar, and sets the current
        * calendar page date to the new month.
        * @method subtractYears
        * @param {Number}	count	The number of years to subtract from the current calendar
        */
        subtractYears: function(count) {
            this.callChildFunction("subtractYears", count);
        },

        /**
        * Returns the Calendar page instance which has a pagedate (month/year) matching the given date. 
        * Returns null if no match is found.
        * 
        * @method getCalendarPage
        * @param {Date} date The JavaScript Date object for which a Calendar page is to be found.
        * @return {Calendar} The Calendar page instance representing the month to which the date 
        * belongs.
        */
        getCalendarPage: function(date) {
            var cal = null;
            if (date) {
                var y = date.getFullYear(),
				m = date.getMonth();

                var pages = this.pages;
                for (var i = 0; i < pages.length; ++i) {
                    var pageDate = pages[i].cfg.getProperty("pagedate");
                    if (pageDate.getFullYear() === y && pageDate.getMonth() === m) {
                        cal = pages[i];
                        break;
                    }
                }
            }
            return cal;
        },

        /**
        * Sets the month on a Date object, taking into account year rollover if the month is less than 0 or greater than 11.
        * The Date object passed in is modified. It should be cloned before passing it into this method if the original value needs to be maintained
        * @method	_setMonthOnDate
        * @private
        * @param	{Date}	date	The Date object on which to set the month index
        * @param	{Number}	iMonth	The month index to set
        */
        _setMonthOnDate: function(date, iMonth) {
            // Bug in Safari 1.3, 2.0 (WebKit build < 420), Date.setMonth does not work consistently if iMonth is not 0-11
            if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420 && (iMonth < 0 || iMonth > 11)) {
                var newDate = DateMath.add(date, DateMath.MONTH, iMonth - date.getMonth());
                date.setTime(newDate.getTime());
            } else {
                date.setMonth(iMonth);
            }
        },

        /**
        * Fixes the width of the CalendarGroup container element, to account for miswrapped floats
        * @method _fixWidth
        * @private
        */
        _fixWidth: function() {
            var w = 0;
            for (var p = 0; p < this.pages.length; ++p) {
                var cal = this.pages[p];
                w += cal.oDomContainer.offsetWidth;
            }
            if (w > 0) {
                this.oDomContainer.style.width = w + "px";
            }
        },

        /**
        * Returns a string representation of the object.
        * @method toString
        * @return {String}	A string representation of the CalendarGroup object.
        */
        toString: function() {
            return "CalendarGroup " + this.id;
        },

        /**
        * Destroys the CalendarGroup instance. The method will remove references
        * to HTML elements, remove any event listeners added by the CalendarGroup.
        * 
        * It will also destroy the Config and CalendarNavigator instances created by the 
        * CalendarGroup and the individual Calendar instances created for each page.
        *
        * @method destroy
        */
        destroy: function() {

            if (this.beforeDestroyEvent.fire()) {

                var cal = this;

                // Child objects
                if (cal.navigator) {
                    cal.navigator.destroy();
                }

                if (cal.cfg) {
                    cal.cfg.destroy();
                }

                // DOM event listeners
                Event.purgeElement(cal.oDomContainer, true);

                // Generated markup/DOM - Not removing the container DIV since we didn't create it.
                Dom.removeClass(cal.oDomContainer, CalendarGroup.CSS_CONTAINER);
                Dom.removeClass(cal.oDomContainer, CalendarGroup.CSS_MULTI_UP);

                for (var i = 0, l = cal.pages.length; i < l; i++) {
                    cal.pages[i].destroy();
                    cal.pages[i] = null;
                }

                cal.oDomContainer.innerHTML = "";

                // JS-to-DOM references
                cal.oDomContainer = null;

                this.destroyEvent.fire();
            }
        }
    };

    /**
    * CSS class representing the container for the calendar
    * @property YAHOO.widget.CalendarGroup.CSS_CONTAINER
    * @static
    * @final
    * @type String
    */
    CalendarGroup.CSS_CONTAINER = "yui-calcontainer";

    /**
    * CSS class representing the container for the calendar
    * @property YAHOO.widget.CalendarGroup.CSS_MULTI_UP
    * @static
    * @final
    * @type String
    */
    CalendarGroup.CSS_MULTI_UP = "multi";

    /**
    * CSS class representing the title for the 2-up calendar
    * @property YAHOO.widget.CalendarGroup.CSS_2UPTITLE
    * @static
    * @final
    * @type String
    */
    CalendarGroup.CSS_2UPTITLE = "title";

    /**
    * CSS class representing the close icon for the 2-up calendar
    * @property YAHOO.widget.CalendarGroup.CSS_2UPCLOSE
    * @static
    * @final
    * @deprecated	Along with Calendar.IMG_ROOT and NAV_ARROW_LEFT, NAV_ARROW_RIGHT configuration properties.
    *					Calendar's <a href="YAHOO.widget.Calendar.html#Style.CSS_CLOSE">Style.CSS_CLOSE</a> property now represents the CSS class used to render the close icon
    * @type String
    */
    CalendarGroup.CSS_2UPCLOSE = "close-icon";

    YAHOO.lang.augmentProto(CalendarGroup, Calendar, "buildDayLabel",
												 "buildMonthLabel",
												 "renderOutOfBoundsDate",
												 "renderRowHeader",
												 "renderRowFooter",
												 "renderCellDefault",
												 "styleCellDefault",
												 "renderCellStyleHighlight1",
												 "renderCellStyleHighlight2",
												 "renderCellStyleHighlight3",
												 "renderCellStyleHighlight4",
												 "renderCellStyleToday",
												 "renderCellStyleSelected",
												 "renderCellNotThisMonth",
												 "renderBodyCellRestricted",
												 "initStyles",
												 "configTitle",
												 "configClose",
												 "configIframe",
												 "configStrings",
												 "configNavigator",
												 "createTitleBar",
												 "createCloseButton",
												 "removeTitleBar",
												 "removeCloseButton",
												 "hide",
												 "show",
												 "toDate",
												 "_toDate",
												 "_parseArgs",
												 "browser");

    YAHOO.widget.CalGrp = CalendarGroup;
    YAHOO.widget.CalendarGroup = CalendarGroup;

    /**
    * @class YAHOO.widget.Calendar2up
    * @extends YAHOO.widget.CalendarGroup
    * @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
    */
    YAHOO.widget.Calendar2up = function(id, containerId, config) {
        this.init(id, containerId, config);
    };

    YAHOO.extend(YAHOO.widget.Calendar2up, CalendarGroup);

    /**
    * @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
    */
    YAHOO.widget.Cal2up = YAHOO.widget.Calendar2up;

})();

/**
* The CalendarNavigator is used along with a Calendar/CalendarGroup to 
* provide a Month/Year popup navigation control, allowing the user to navigate 
* to a specific month/year in the Calendar/CalendarGroup without having to 
* scroll through months sequentially
*
* @namespace YAHOO.widget
* @class CalendarNavigator
* @constructor
* @param {Calendar|CalendarGroup} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached.
*/
YAHOO.widget.CalendarNavigator = function(cal) {
    this.init(cal);
};

(function() {
    // Setup static properties (inside anon fn, so that we can use shortcuts)
    var CN = YAHOO.widget.CalendarNavigator;

    /**
    * YAHOO.widget.CalendarNavigator.CLASSES contains constants
    * for the class values applied to the CalendarNaviatgator's 
    * DOM elements
    * @property YAHOO.widget.CalendarNavigator.CLASSES
    * @type Object
    * @static
    */
    CN.CLASSES = {
        /**
        * Class applied to the Calendar Navigator's bounding box
        * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV
        * @type String
        * @static
        */
        NAV: "yui-cal-nav",
        /**
        * Class applied to the Calendar/CalendarGroup's bounding box to indicate
        * the Navigator is currently visible
        * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV_VISIBLE
        * @type String
        * @static
        */
        NAV_VISIBLE: "yui-cal-nav-visible",
        /**
        * Class applied to the Navigator mask's bounding box
        * @property YAHOO.widget.CalendarNavigator.CLASSES.MASK
        * @type String
        * @static
        */
        MASK: "yui-cal-nav-mask",
        /**
        * Class applied to the year label/control bounding box
        * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR
        * @type String
        * @static
        */
        YEAR: "yui-cal-nav-y",
        /**
        * Class applied to the month label/control bounding box
        * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH
        * @type String
        * @static
        */
        MONTH: "yui-cal-nav-m",
        /**
        * Class applied to the submit/cancel button's bounding box
        * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTONS
        * @type String
        * @static
        */
        BUTTONS: "yui-cal-nav-b",
        /**
        * Class applied to buttons wrapping element
        * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTON
        * @type String
        * @static
        */
        BUTTON: "yui-cal-nav-btn",
        /**
        * Class applied to the validation error area's bounding box
        * @property YAHOO.widget.CalendarNavigator.CLASSES.ERROR
        * @type String
        * @static
        */
        ERROR: "yui-cal-nav-e",
        /**
        * Class applied to the year input control
        * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR_CTRL
        * @type String
        * @static
        */
        YEAR_CTRL: "yui-cal-nav-yc",
        /**
        * Class applied to the month input control
        * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH_CTRL
        * @type String
        * @static
        */
        MONTH_CTRL: "yui-cal-nav-mc",
        /**
        * Class applied to controls with invalid data (e.g. a year input field with invalid an year)
        * @property YAHOO.widget.CalendarNavigator.CLASSES.INVALID
        * @type String
        * @static
        */
        INVALID: "yui-invalid",
        /**
        * Class applied to default controls
        * @property YAHOO.widget.CalendarNavigator.CLASSES.DEFAULT
        * @type String
        * @static
        */
        DEFAULT: "yui-default"
    };

    /**
    * Object literal containing the default configuration values for the CalendarNavigator
    * The configuration object is expected to follow the format below, with the properties being
    * case sensitive.
    * <dl>
    * <dt>strings</dt>
    * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI
    *     <dl>
    *         <dt>month</dt><dd><em>String</em> : The string to use for the month label. Defaults to "Month".</dd>
    *         <dt>year</dt><dd><em>String</em> : The string to use for the year label. Defaults to "Year".</dd>
    *         <dt>submit</dt><dd><em>String</em> : The string to use for the submit button label. Defaults to "Okay".</dd>
    *         <dt>cancel</dt><dd><em>String</em> : The string to use for the cancel button label. Defaults to "Cancel".</dd>
    *         <dt>invalidYear</dt><dd><em>String</em> : The string to use for invalid year values. Defaults to "Year needs to be a number".</dd>
    *     </dl>
    * </dd>
    * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
    * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
    * </dl>
    * @property _DEFAULT_CFG
    * @protected
    * @type Object
    * @static
    */
    CN._DEFAULT_CFG = {
        strings: {
            month: "Month",
            year: "Year",
            submit: "Okay",
            cancel: "Cancel",
            invalidYear: "Year needs to be a number"
        },
        monthFormat: YAHOO.widget.Calendar.LONG,
        initialFocus: "year"
    };

    /**
    * The suffix added to the Calendar/CalendarGroup's ID, to generate
    * a unique ID for the Navigator and it's bounding box.
    * @property YAHOO.widget.CalendarNavigator.ID_SUFFIX
    * @static
    * @type String
    * @final
    */
    CN.ID_SUFFIX = "_nav";
    /**
    * The suffix added to the Navigator's ID, to generate
    * a unique ID for the month control.
    * @property YAHOO.widget.CalendarNavigator.MONTH_SUFFIX
    * @static
    * @type String 
    * @final
    */
    CN.MONTH_SUFFIX = "_month";
    /**
    * The suffix added to the Navigator's ID, to generate
    * a unique ID for the year control.
    * @property YAHOO.widget.CalendarNavigator.YEAR_SUFFIX
    * @static
    * @type String
    * @final
    */
    CN.YEAR_SUFFIX = "_year";
    /**
    * The suffix added to the Navigator's ID, to generate
    * a unique ID for the error bounding box.
    * @property YAHOO.widget.CalendarNavigator.ERROR_SUFFIX
    * @static
    * @type String
    * @final
    */
    CN.ERROR_SUFFIX = "_error";
    /**
    * The suffix added to the Navigator's ID, to generate
    * a unique ID for the "Cancel" button.
    * @property YAHOO.widget.CalendarNavigator.CANCEL_SUFFIX
    * @static
    * @type String
    * @final
    */
    CN.CANCEL_SUFFIX = "_cancel";
    /**
    * The suffix added to the Navigator's ID, to generate
    * a unique ID for the "Submit" button.
    * @property YAHOO.widget.CalendarNavigator.SUBMIT_SUFFIX
    * @static
    * @type String
    * @final
    */
    CN.SUBMIT_SUFFIX = "_submit";

    /**
    * The number of digits to which the year input control is to be limited.
    * @property YAHOO.widget.CalendarNavigator.YR_MAX_DIGITS
    * @static
    * @type Number
    */
    CN.YR_MAX_DIGITS = 4;

    /**
    * The amount by which to increment the current year value,
    * when the arrow up/down key is pressed on the year control
    * @property YAHOO.widget.CalendarNavigator.YR_MINOR_INC
    * @static
    * @type Number
    */
    CN.YR_MINOR_INC = 1;

    /**
    * The amount by which to increment the current year value,
    * when the page up/down key is pressed on the year control
    * @property YAHOO.widget.CalendarNavigator.YR_MAJOR_INC
    * @static
    * @type Number
    */
    CN.YR_MAJOR_INC = 10;

    /**
    * Artificial delay (in ms) between the time the Navigator is hidden
    * and the Calendar/CalendarGroup state is updated. Allows the user
    * the see the Calendar/CalendarGroup page changing. If set to 0
    * the Calendar/CalendarGroup page will be updated instantly
    * @property YAHOO.widget.CalendarNavigator.UPDATE_DELAY
    * @static
    * @type Number
    */
    CN.UPDATE_DELAY = 50;

    /**
    * Regular expression used to validate the year input
    * @property YAHOO.widget.CalendarNavigator.YR_PATTERN
    * @static
    * @type RegExp
    */
    CN.YR_PATTERN = /^\d+$/;
    /**
    * Regular expression used to trim strings
    * @property YAHOO.widget.CalendarNavigator.TRIM
    * @static
    * @type RegExp
    */
    CN.TRIM = /^\s*(.*?)\s*$/;
})();

YAHOO.widget.CalendarNavigator.prototype = {

    /**
    * The unique ID for this CalendarNavigator instance
    * @property id
    * @type String
    */
    id: null,

    /**
    * The Calendar/CalendarGroup instance to which the navigator belongs
    * @property cal
    * @type {Calendar|CalendarGroup}
    */
    cal: null,

    /**
    * Reference to the HTMLElement used to render the navigator's bounding box
    * @property navEl
    * @type HTMLElement
    */
    navEl: null,

    /**
    * Reference to the HTMLElement used to render the navigator's mask
    * @property maskEl
    * @type HTMLElement
    */
    maskEl: null,

    /**
    * Reference to the HTMLElement used to input the year
    * @property yearEl
    * @type HTMLElement
    */
    yearEl: null,

    /**
    * Reference to the HTMLElement used to input the month
    * @property monthEl
    * @type HTMLElement
    */
    monthEl: null,

    /**
    * Reference to the HTMLElement used to display validation errors
    * @property errorEl
    * @type HTMLElement
    */
    errorEl: null,

    /**
    * Reference to the HTMLElement used to update the Calendar/Calendar group
    * with the month/year values
    * @property submitEl
    * @type HTMLElement
    */
    submitEl: null,

    /**
    * Reference to the HTMLElement used to hide the navigator without updating the 
    * Calendar/Calendar group
    * @property cancelEl
    * @type HTMLElement
    */
    cancelEl: null,

    /** 
    * Reference to the first focusable control in the navigator (by default monthEl)
    * @property firstCtrl
    * @type HTMLElement
    */
    firstCtrl: null,

    /** 
    * Reference to the last focusable control in the navigator (by default cancelEl)
    * @property lastCtrl
    * @type HTMLElement
    */
    lastCtrl: null,

    /**
    * The document containing the Calendar/Calendar group instance
    * @protected
    * @property _doc
    * @type HTMLDocument
    */
    _doc: null,

    /**
    * Internal state property for the current year displayed in the navigator
    * @protected
    * @property _year
    * @type Number
    */
    _year: null,

    /**
    * Internal state property for the current month index displayed in the navigator
    * @protected
    * @property _month
    * @type Number
    */
    _month: 0,

    /**
    * Private internal state property which indicates whether or not the 
    * Navigator has been rendered.
    * @private
    * @property __rendered
    * @type Boolean
    */
    __rendered: false,

    /**
    * Init lifecycle method called as part of construction
    * 
    * @method init
    * @param {Calendar} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached
    */
    init: function(cal) {
        var calBox = cal.oDomContainer;

        this.cal = cal;
        this.id = calBox.id + YAHOO.widget.CalendarNavigator.ID_SUFFIX;
        this._doc = calBox.ownerDocument;

        /**
        * Private flag, to identify IE6/IE7 Quirks
        * @private
        * @property __isIEQuirks
        */
        var ie = YAHOO.env.ua.ie;
        this.__isIEQuirks = (ie && ((ie <= 6) || (ie === 7 && this._doc.compatMode == "BackCompat")));
    },

    /**
    * Displays the navigator and mask, updating the input controls to reflect the 
    * currently set month and year. The show method will invoke the render method
    * if the navigator has not been renderered already, allowing for lazy rendering
    * of the control.
    * 
    * The show method will fire the Calendar/CalendarGroup's beforeShowNav and showNav events
    * 
    * @method show
    */
    show: function() {
        var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;

        if (this.cal.beforeShowNavEvent.fire()) {
            if (!this.__rendered) {
                this.render();
            }
            this.clearErrors();

            this._updateMonthUI();
            this._updateYearUI();
            this._show(this.navEl, true);

            this.setInitialFocus();
            this.showMask();

            YAHOO.util.Dom.addClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
            this.cal.showNavEvent.fire();
        }
    },

    /**
    * Hides the navigator and mask
    * 
    * The show method will fire the Calendar/CalendarGroup's beforeHideNav event and hideNav events
    * @method hide
    */
    hide: function() {
        var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;

        if (this.cal.beforeHideNavEvent.fire()) {
            this._show(this.navEl, false);
            this.hideMask();
            YAHOO.util.Dom.removeClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
            this.cal.hideNavEvent.fire();
        }
    },


    /**
    * Displays the navigator's mask element
    * 
    * @method showMask
    */
    showMask: function() {
        this._show(this.maskEl, true);
        if (this.__isIEQuirks) {
            this._syncMask();
        }
    },

    /**
    * Hides the navigator's mask element
    * 
    * @method hideMask
    */
    hideMask: function() {
        this._show(this.maskEl, false);
    },

    /**
    * Returns the current month set on the navigator
    * 
    * Note: This may not be the month set in the UI, if 
    * the UI contains an invalid value.
    * 
    * @method getMonth
    * @return {Number} The Navigator's current month index
    */
    getMonth: function() {
        return this._month;
    },

    /**
    * Returns the current year set on the navigator
    * 
    * Note: This may not be the year set in the UI, if 
    * the UI contains an invalid value.
    * 
    * @method getYear
    * @return {Number} The Navigator's current year value
    */
    getYear: function() {
        return this._year;
    },

    /**
    * Sets the current month on the Navigator, and updates the UI
    * 
    * @method setMonth
    * @param {Number} nMonth The month index, from 0 (Jan) through 11 (Dec).
    */
    setMonth: function(nMonth) {
        if (nMonth >= 0 && nMonth < 12) {
            this._month = nMonth;
        }
        this._updateMonthUI();
    },

    /**
    * Sets the current year on the Navigator, and updates the UI. If the 
    * provided year is invalid, it will not be set.
    * 
    * @method setYear
    * @param {Number} nYear The full year value to set the Navigator to.
    */
    setYear: function(nYear) {
        var yrPattern = YAHOO.widget.CalendarNavigator.YR_PATTERN;
        if (YAHOO.lang.isNumber(nYear) && yrPattern.test(nYear + "")) {
            this._year = nYear;
        }
        this._updateYearUI();
    },

    /**
    * Renders the HTML for the navigator, adding it to the 
    * document and attaches event listeners if it has not 
    * already been rendered.
    * 
    * @method render
    */
    render: function() {
        this.cal.beforeRenderNavEvent.fire();
        if (!this.__rendered) {
            this.createNav();
            this.createMask();
            this.applyListeners();
            this.__rendered = true;
        }
        this.cal.renderNavEvent.fire();
    },

    /**
    * Creates the navigator's containing HTMLElement, it's contents, and appends 
    * the containg element to the Calendar/CalendarGroup's container.
    * 
    * @method createNav
    */
    createNav: function() {
        var NAV = YAHOO.widget.CalendarNavigator;
        var doc = this._doc;

        var d = doc.createElement("div");
        d.className = NAV.CLASSES.NAV;

        var htmlBuf = this.renderNavContents([]);

        d.innerHTML = htmlBuf.join('');
        this.cal.oDomContainer.appendChild(d);

        this.navEl = d;

        this.yearEl = doc.getElementById(this.id + NAV.YEAR_SUFFIX);
        this.monthEl = doc.getElementById(this.id + NAV.MONTH_SUFFIX);
        this.errorEl = doc.getElementById(this.id + NAV.ERROR_SUFFIX);
        this.submitEl = doc.getElementById(this.id + NAV.SUBMIT_SUFFIX);
        this.cancelEl = doc.getElementById(this.id + NAV.CANCEL_SUFFIX);

        if (YAHOO.env.ua.gecko && this.yearEl && this.yearEl.type == "text") {
            // Avoid XUL error on focus, select [ https://bugzilla.mozilla.org/show_bug.cgi?id=236791, 
            // supposedly fixed in 1.8.1, but there are reports of it still being around for methods other than blur ]
            this.yearEl.setAttribute("autocomplete", "off");
        }

        this._setFirstLastElements();
    },

    /**
    * Creates the Mask HTMLElement and appends it to the Calendar/CalendarGroups
    * container.
    * 
    * @method createMask
    */
    createMask: function() {
        var C = YAHOO.widget.CalendarNavigator.CLASSES;

        var d = this._doc.createElement("div");
        d.className = C.MASK;

        this.cal.oDomContainer.appendChild(d);
        this.maskEl = d;
    },

    /**
    * Used to set the width/height of the mask in pixels to match the Calendar Container.
    * Currently only used for IE6 and IE7 quirks mode. The other A-Grade browser are handled using CSS (width/height 100%).
    * <p>
    * The method is also registered as an HTMLElement resize listener on the Calendars container element.
    * </p>
    * @protected
    * @method _syncMask
    */
    _syncMask: function() {
        var c = this.cal.oDomContainer;
        if (c && this.maskEl) {
            var r = YAHOO.util.Dom.getRegion(c);
            YAHOO.util.Dom.setStyle(this.maskEl, "width", r.right - r.left + "px");
            YAHOO.util.Dom.setStyle(this.maskEl, "height", r.bottom - r.top + "px");
        }
    },

    /**
    * Renders the contents of the navigator
    * 
    * @method renderNavContents
    * 
    * @param {Array} html The HTML buffer to append the HTML to.
    * @return {Array} A reference to the buffer passed in.
    */
    renderNavContents: function(html) {
        var NAV = YAHOO.widget.CalendarNavigator,
			C = NAV.CLASSES,
			h = html; // just to use a shorter name

        h[h.length] = '<div class="' + C.MONTH + '">';
        this.renderMonth(h);
        h[h.length] = '</div>';
        h[h.length] = '<div class="' + C.YEAR + '">';
        this.renderYear(h);
        h[h.length] = '</div>';
        h[h.length] = '<div class="' + C.BUTTONS + '">';
        this.renderButtons(h);
        h[h.length] = '</div>';
        h[h.length] = '<div class="' + C.ERROR + '" id="' + this.id + NAV.ERROR_SUFFIX + '"></div>';

        return h;
    },

    /**
    * Renders the month label and control for the navigator
    * 
    * @method renderNavContents
    * @param {Array} html The HTML buffer to append the HTML to.
    * @return {Array} A reference to the buffer passed in.
    */
    renderMonth: function(html) {
        var NAV = YAHOO.widget.CalendarNavigator,
			C = NAV.CLASSES;

        var id = this.id + NAV.MONTH_SUFFIX,
			mf = this.__getCfg("monthFormat"),
			months = this.cal.cfg.getProperty((mf == YAHOO.widget.Calendar.SHORT) ? "MONTHS_SHORT" : "MONTHS_LONG"),
			h = html;

        if (months && months.length > 0) {
            h[h.length] = '<label for="' + id + '">';
            h[h.length] = this.__getCfg("month", true);
            h[h.length] = '</label>';
            h[h.length] = '<select name="' + id + '" id="' + id + '" class="' + C.MONTH_CTRL + '">';
            for (var i = 0; i < months.length; i++) {
                h[h.length] = '<option value="' + i + '">';
                h[h.length] = months[i];
                h[h.length] = '</option>';
            }
            h[h.length] = '</select>';
        }
        return h;
    },

    /**
    * Renders the year label and control for the navigator
    * 
    * @method renderYear
    * @param {Array} html The HTML buffer to append the HTML to.
    * @return {Array} A reference to the buffer passed in.
    */
    renderYear: function(html) {
        var NAV = YAHOO.widget.CalendarNavigator,
			C = NAV.CLASSES;

        var id = this.id + NAV.YEAR_SUFFIX,
			size = NAV.YR_MAX_DIGITS,
			h = html;

        h[h.length] = '<label for="' + id + '">';
        h[h.length] = this.__getCfg("year", true);
        h[h.length] = '</label>';
        h[h.length] = '<input type="text" name="' + id + '" id="' + id + '" class="' + C.YEAR_CTRL + '" maxlength="' + size + '"/>';
        return h;
    },

    /**
    * Renders the submit/cancel buttons for the navigator
    * 
    * @method renderButton
    * @return {String} The HTML created for the Button UI
    */
    renderButtons: function(html) {
        var C = YAHOO.widget.CalendarNavigator.CLASSES;
        var h = html;

        h[h.length] = '<span class="' + C.BUTTON + ' ' + C.DEFAULT + '">';
        h[h.length] = '<button type="button" id="' + this.id + '_submit' + '">';
        h[h.length] = this.__getCfg("submit", true);
        h[h.length] = '</button>';
        h[h.length] = '</span>';
        h[h.length] = '<span class="' + C.BUTTON + '">';
        h[h.length] = '<button type="button" id="' + this.id + '_cancel' + '">';
        h[h.length] = this.__getCfg("cancel", true);
        h[h.length] = '</button>';
        h[h.length] = '</span>';

        return h;
    },

    /**
    * Attaches DOM event listeners to the rendered elements
    * <p>
    * The method will call applyKeyListeners, to setup keyboard specific 
    * listeners
    * </p>
    * @method applyListeners
    */
    applyListeners: function() {
        var E = YAHOO.util.Event;

        function yearUpdateHandler() {
            if (this.validate()) {
                this.setYear(this._getYearFromUI());
            }
        }

        function monthUpdateHandler() {
            this.setMonth(this._getMonthFromUI());
        }

        E.on(this.submitEl, "click", this.submit, this, true);
        E.on(this.cancelEl, "click", this.cancel, this, true);
        E.on(this.yearEl, "blur", yearUpdateHandler, this, true);
        E.on(this.monthEl, "change", monthUpdateHandler, this, true);

        if (this.__isIEQuirks) {
            YAHOO.util.Event.on(this.cal.oDomContainer, "resize", this._syncMask, this, true);
        }

        this.applyKeyListeners();
    },

    /**
    * Removes/purges DOM event listeners from the rendered elements
    * 
    * @method purgeListeners
    */
    purgeListeners: function() {
        var E = YAHOO.util.Event;
        E.removeListener(this.submitEl, "click", this.submit);
        E.removeListener(this.cancelEl, "click", this.cancel);
        E.removeListener(this.yearEl, "blur");
        E.removeListener(this.monthEl, "change");
        if (this.__isIEQuirks) {
            E.removeListener(this.cal.oDomContainer, "resize", this._syncMask);
        }

        this.purgeKeyListeners();
    },

    /**
    * Attaches DOM listeners for keyboard support. 
    * Tab/Shift-Tab looping, Enter Key Submit on Year element,
    * Up/Down/PgUp/PgDown year increment on Year element
    * <p>
    * NOTE: MacOSX Safari 2.x doesn't let you tab to buttons and 
    * MacOSX Gecko does not let you tab to buttons or select controls,
    * so for these browsers, Tab/Shift-Tab looping is limited to the 
    * elements which can be reached using the tab key.
    * </p>
    * @method applyKeyListeners
    */
    applyKeyListeners: function() {
        var E = YAHOO.util.Event,
			ua = YAHOO.env.ua;

        // IE/Safari 3.1 doesn't fire keypress for arrow/pg keys (non-char keys)
        var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";

        // - IE/Safari 3.1 doesn't fire keypress for non-char keys
        // - Opera doesn't allow us to cancel keydown or keypress for tab, but 
        //   changes focus successfully on keydown (keypress is too late to change focus - opera's already moved on).
        var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";

        // Everyone likes keypress for Enter (char keys) - whoo hoo!
        E.on(this.yearEl, "keypress", this._handleEnterKey, this, true);

        E.on(this.yearEl, arrowEvt, this._handleDirectionKeys, this, true);
        E.on(this.lastCtrl, tabEvt, this._handleTabKey, this, true);
        E.on(this.firstCtrl, tabEvt, this._handleShiftTabKey, this, true);
    },

    /**
    * Removes/purges DOM listeners for keyboard support
    *
    * @method purgeKeyListeners
    */
    purgeKeyListeners: function() {
        var E = YAHOO.util.Event,
			ua = YAHOO.env.ua;

        var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
        var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";

        E.removeListener(this.yearEl, "keypress", this._handleEnterKey);
        E.removeListener(this.yearEl, arrowEvt, this._handleDirectionKeys);
        E.removeListener(this.lastCtrl, tabEvt, this._handleTabKey);
        E.removeListener(this.firstCtrl, tabEvt, this._handleShiftTabKey);
    },

    /**
    * Updates the Calendar/CalendarGroup's pagedate with the currently set month and year if valid.
    * <p>
    * If the currently set month/year is invalid, a validation error will be displayed and the 
    * Calendar/CalendarGroup's pagedate will not be updated.
    * </p>
    * @method submit
    */
    submit: function() {
        if (this.validate()) {
            this.hide();

            this.setMonth(this._getMonthFromUI());
            this.setYear(this._getYearFromUI());

            var cal = this.cal;

            // Artificial delay, just to help the user see something changed
            var delay = YAHOO.widget.CalendarNavigator.UPDATE_DELAY;
            if (delay > 0) {
                var nav = this;
                window.setTimeout(function() { nav._update(cal); }, delay);
            } else {
                this._update(cal);
            }
        }
    },

    /**
    * Updates the Calendar rendered state, based on the state of the CalendarNavigator
    * @method _update
    * @param cal The Calendar instance to update
    * @protected
    */
    _update: function(cal) {
        cal.setYear(this.getYear());
        cal.setMonth(this.getMonth());
        cal.render();
    },

    /**
    * Hides the navigator and mask, without updating the Calendar/CalendarGroup's state
    * 
    * @method cancel
    */
    cancel: function() {
        this.hide();
    },

    /**
    * Validates the current state of the UI controls
    * 
    * @method validate
    * @return {Boolean} true, if the current UI state contains valid values, false if not
    */
    validate: function() {
        if (this._getYearFromUI() !== null) {
            this.clearErrors();
            return true;
        } else {
            this.setYearError();
            this.setError(this.__getCfg("invalidYear", true));
            return false;
        }
    },

    /**
    * Displays an error message in the Navigator's error panel
    * @method setError
    * @param {String} msg The error message to display
    */
    setError: function(msg) {
        if (this.errorEl) {
            this.errorEl.innerHTML = msg;
            this._show(this.errorEl, true);
        }
    },

    /**
    * Clears the navigator's error message and hides the error panel
    * @method clearError 
    */
    clearError: function() {
        if (this.errorEl) {
            this.errorEl.innerHTML = "";
            this._show(this.errorEl, false);
        }
    },

    /**
    * Displays the validation error UI for the year control
    * @method setYearError
    */
    setYearError: function() {
        YAHOO.util.Dom.addClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
    },

    /**
    * Removes the validation error UI for the year control
    * @method clearYearError
    */
    clearYearError: function() {
        YAHOO.util.Dom.removeClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
    },

    /**
    * Clears all validation and error messages in the UI
    * @method clearErrors
    */
    clearErrors: function() {
        this.clearError();
        this.clearYearError();
    },

    /**
    * Sets the initial focus, based on the configured value
    * @method setInitialFocus
    */
    setInitialFocus: function() {
        var el = this.submitEl,
			f = this.__getCfg("initialFocus");

        if (f && f.toLowerCase) {
            f = f.toLowerCase();
            if (f == "year") {
                el = this.yearEl;
                try {
                    this.yearEl.select();
                } catch (selErr) {
                    // Ignore;
                }
            } else if (f == "month") {
                el = this.monthEl;
            }
        }

        if (el && YAHOO.lang.isFunction(el.focus)) {
            try {
                el.focus();
            } catch (focusErr) {
                // TODO: Fall back if focus fails?
            }
        }
    },

    /**
    * Removes all renderered HTML elements for the Navigator from
    * the DOM, purges event listeners and clears (nulls) any property
    * references to HTML references
    * @method erase
    */
    erase: function() {
        if (this.__rendered) {
            this.purgeListeners();

            // Clear out innerHTML references
            this.yearEl = null;
            this.monthEl = null;
            this.errorEl = null;
            this.submitEl = null;
            this.cancelEl = null;
            this.firstCtrl = null;
            this.lastCtrl = null;
            if (this.navEl) {
                this.navEl.innerHTML = "";
            }

            var p = this.navEl.parentNode;
            if (p) {
                p.removeChild(this.navEl);
            }
            this.navEl = null;

            var pm = this.maskEl.parentNode;
            if (pm) {
                pm.removeChild(this.maskEl);
            }
            this.maskEl = null;
            this.__rendered = false;
        }
    },

    /**
    * Destroys the Navigator object and any HTML references
    * @method destroy
    */
    destroy: function() {
        this.erase();
        this._doc = null;
        this.cal = null;
        this.id = null;
    },

    /**
    * Protected implementation to handle how UI elements are 
    * hidden/shown.
    *
    * @method _show
    * @protected
    */
    _show: function(el, bShow) {
        if (el) {
            YAHOO.util.Dom.setStyle(el, "display", (bShow) ? "block" : "none");
        }
    },

    /**
    * Returns the month value (index), from the month UI element
    * @protected
    * @method _getMonthFromUI
    * @return {Number} The month index, or 0 if a UI element for the month
    * is not found
    */
    _getMonthFromUI: function() {
        if (this.monthEl) {
            return this.monthEl.selectedIndex;
        } else {
            return 0; // Default to Jan
        }
    },

    /**
    * Returns the year value, from the Navitator's year UI element
    * @protected
    * @method _getYearFromUI
    * @return {Number} The year value set in the UI, if valid. null is returned if 
    * the UI does not contain a valid year value.
    */
    _getYearFromUI: function() {
        var NAV = YAHOO.widget.CalendarNavigator;

        var yr = null;
        if (this.yearEl) {
            var value = this.yearEl.value;
            value = value.replace(NAV.TRIM, "$1");

            if (NAV.YR_PATTERN.test(value)) {
                yr = parseInt(value, 10);
            }
        }
        return yr;
    },

    /**
    * Updates the Navigator's year UI, based on the year value set on the Navigator object
    * @protected
    * @method _updateYearUI
    */
    _updateYearUI: function() {
        if (this.yearEl && this._year !== null) {
            this.yearEl.value = this._year;
        }
    },

    /**
    * Updates the Navigator's month UI, based on the month value set on the Navigator object
    * @protected
    * @method _updateMonthUI
    */
    _updateMonthUI: function() {
        if (this.monthEl) {
            this.monthEl.selectedIndex = this._month;
        }
    },

    /**
    * Sets up references to the first and last focusable element in the Navigator's UI
    * in terms of tab order (Naviagator's firstEl and lastEl properties). The references
    * are used to control modality by looping around from the first to the last control
    * and visa versa for tab/shift-tab navigation.
    * <p>
    * See <a href="#applyKeyListeners">applyKeyListeners</a>
    * </p>
    * @protected
    * @method _setFirstLastElements
    */
    _setFirstLastElements: function() {
        this.firstCtrl = this.monthEl;
        this.lastCtrl = this.cancelEl;

        // Special handling for MacOSX.
        // - Safari 2.x can't focus on buttons
        // - Gecko can't focus on select boxes or buttons
        if (this.__isMac) {
            if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420) {
                this.firstCtrl = this.monthEl;
                this.lastCtrl = this.yearEl;
            }
            if (YAHOO.env.ua.gecko) {
                this.firstCtrl = this.yearEl;
                this.lastCtrl = this.yearEl;
            }
        }
    },

    /**
    * Default Keyboard event handler to capture Enter 
    * on the Navigator's year control (yearEl)
    * 
    * @method _handleEnterKey
    * @protected
    * @param {Event} e The DOM event being handled
    */
    _handleEnterKey: function(e) {
        var KEYS = YAHOO.util.KeyListener.KEY;

        if (YAHOO.util.Event.getCharCode(e) == KEYS.ENTER) {
            YAHOO.util.Event.preventDefault(e);
            this.submit();
        }
    },

    /**
    * Default Keyboard event handler to capture up/down/pgup/pgdown
    * on the Navigator's year control (yearEl).
    * 
    * @method _handleDirectionKeys
    * @protected
    * @param {Event} e The DOM event being handled
    */
    _handleDirectionKeys: function(e) {
        var E = YAHOO.util.Event,
			KEYS = YAHOO.util.KeyListener.KEY,
			NAV = YAHOO.widget.CalendarNavigator;

        var value = (this.yearEl.value) ? parseInt(this.yearEl.value, 10) : null;
        if (isFinite(value)) {
            var dir = false;
            switch (E.getCharCode(e)) {
                case KEYS.UP:
                    this.yearEl.value = value + NAV.YR_MINOR_INC;
                    dir = true;
                    break;
                case KEYS.DOWN:
                    this.yearEl.value = Math.max(value - NAV.YR_MINOR_INC, 0);
                    dir = true;
                    break;
                case KEYS.PAGE_UP:
                    this.yearEl.value = value + NAV.YR_MAJOR_INC;
                    dir = true;
                    break;
                case KEYS.PAGE_DOWN:
                    this.yearEl.value = Math.max(value - NAV.YR_MAJOR_INC, 0);
                    dir = true;
                    break;
                default:
                    break;
            }
            if (dir) {
                E.preventDefault(e);
                try {
                    this.yearEl.select();
                } catch (err) {
                    // Ignore
                }
            }
        }
    },

    /**
    * Default Keyboard event handler to capture Tab 
    * on the last control (lastCtrl) in the Navigator.
    * 
    * @method _handleTabKey
    * @protected
    * @param {Event} e The DOM event being handled
    */
    _handleTabKey: function(e) {
        var E = YAHOO.util.Event,
			KEYS = YAHOO.util.KeyListener.KEY;

        if (E.getCharCode(e) == KEYS.TAB && !e.shiftKey) {
            try {
                E.preventDefault(e);
                this.firstCtrl.focus();
            } catch (err) {
                // Ignore - mainly for focus edge cases
            }
        }
    },

    /**
    * Default Keyboard event handler to capture Shift-Tab 
    * on the first control (firstCtrl) in the Navigator.
    * 
    * @method _handleShiftTabKey
    * @protected
    * @param {Event} e The DOM event being handled
    */
    _handleShiftTabKey: function(e) {
        var E = YAHOO.util.Event,
			KEYS = YAHOO.util.KeyListener.KEY;

        if (e.shiftKey && E.getCharCode(e) == KEYS.TAB) {
            try {
                E.preventDefault(e);
                this.lastCtrl.focus();
            } catch (err) {
                // Ignore - mainly for focus edge cases
            }
        }
    },

    /**
    * Retrieve Navigator configuration values from 
    * the parent Calendar/CalendarGroup's config value.
    * <p>
    * If it has not been set in the user provided configuration, the method will 
    * return the default value of the configuration property, as set in _DEFAULT_CFG
    * </p>
    * @private
    * @method __getCfg
    * @param {String} Case sensitive property name.
    * @param {Boolean} true, if the property is a string property, false if not.
    * @return The value of the configuration property
    */
    __getCfg: function(prop, bIsStr) {
        var DEF_CFG = YAHOO.widget.CalendarNavigator._DEFAULT_CFG;
        var cfg = this.cal.cfg.getProperty("navigator");

        if (bIsStr) {
            return (cfg !== true && cfg.strings && cfg.strings[prop]) ? cfg.strings[prop] : DEF_CFG.strings[prop];
        } else {
            return (cfg !== true && cfg[prop]) ? cfg[prop] : DEF_CFG[prop];
        }
    },

    /**
    * Private flag, to identify MacOS
    * @private
    * @property __isMac
    */
    __isMac: (navigator.userAgent.toLowerCase().indexOf("macintosh") != -1)

};

YAHOO.register("calendar", YAHOO.widget.Calendar, { version: "2.6.0", build: "1321" });


/**
* Version: 1.0 Alpha-1 
* Build Date: 13-Nov-2007
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. 
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
*/
Date.CultureInfo = { name: "en-US", englishName: "English (United States)", nativeName: "English (United States)", dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], abbreviatedDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], shortestDayNames: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], firstLetterDayNames: ["S", "M", "T", "W", "T", "F", "S"], monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], abbreviatedMonthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], amDesignator: "AM", pmDesignator: "PM", firstDayOfWeek: 0, twoDigitYearMax: 2029, dateElementOrder: "mdy", formatPatterns: { shortDate: "M/d/yyyy", longDate: "dddd, MMMM dd, yyyy", shortTime: "h:mm tt", longTime: "h:mm:ss tt", fullDateTime: "dddd, MMMM dd, yyyy h:mm:ss tt", sortableDateTime: "yyyy-MM-ddTHH:mm:ss", universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ", rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT", monthDay: "MMMM dd", yearMonth: "MMMM, yyyy" }, regexPatterns: { jan: /^jan(uary)?/i, feb: /^feb(ruary)?/i, mar: /^mar(ch)?/i, apr: /^apr(il)?/i, may: /^may/i, jun: /^jun(e)?/i, jul: /^jul(y)?/i, aug: /^aug(ust)?/i, sep: /^sep(t(ember)?)?/i, oct: /^oct(ober)?/i, nov: /^nov(ember)?/i, dec: /^dec(ember)?/i, sun: /^su(n(day)?)?/i, mon: /^mo(n(day)?)?/i, tue: /^tu(e(s(day)?)?)?/i, wed: /^we(d(nesday)?)?/i, thu: /^th(u(r(s(day)?)?)?)?/i, fri: /^fr(i(day)?)?/i, sat: /^sa(t(urday)?)?/i, future: /^next/i, past: /^last|past|prev(ious)?/i, add: /^(\+|after|from)/i, subtract: /^(\-|before|ago)/i, yesterday: /^yesterday/i, today: /^t(oday)?/i, tomorrow: /^tomorrow/i, now: /^n(ow)?/i, millisecond: /^ms|milli(second)?s?/i, second: /^sec(ond)?s?/i, minute: /^min(ute)?s?/i, hour: /^h(ou)?rs?/i, week: /^w(ee)?k/i, month: /^m(o(nth)?s?)?/i, day: /^d(ays?)?/i, year: /^y((ea)?rs?)?/i, shortMeridian: /^(a|p)/i, longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i, timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i, ordinalSuffix: /^\s*(st|nd|rd|th)/i, timeContext: /^\s*(\:|a|p)/i }, abbreviatedTimeZoneStandard: { GMT: "-000", EST: "-0400", CST: "-0500", MST: "-0600", PST: "-0700" }, abbreviatedTimeZoneDST: { GMT: "-000", EDT: "-0500", CDT: "-0600", MDT: "-0700", PDT: "-0800"} };
Date.getMonthNumberFromName = function(name) {
    var n = Date.CultureInfo.monthNames, m = Date.CultureInfo.abbreviatedMonthNames, s = name.toLowerCase(); for (var i = 0; i < n.length; i++) { if (n[i].toLowerCase() == s || m[i].toLowerCase() == s) { return i; } }
    return -1;
}; Date.getDayNumberFromName = function(name) {
    var n = Date.CultureInfo.dayNames, m = Date.CultureInfo.abbreviatedDayNames, o = Date.CultureInfo.shortestDayNames, s = name.toLowerCase(); for (var i = 0; i < n.length; i++) { if (n[i].toLowerCase() == s || m[i].toLowerCase() == s) { return i; } }
    return -1;
}; Date.isLeapYear = function(year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); }; Date.getDaysInMonth = function(year, month) { return [31, (Date.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; }; Date.getTimezoneOffset = function(s, dst) { return (dst || false) ? Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()] : Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()]; }; Date.getTimezoneAbbreviation = function(offset, dst) {
    var n = (dst || false) ? Date.CultureInfo.abbreviatedTimeZoneDST : Date.CultureInfo.abbreviatedTimeZoneStandard, p; for (p in n) { if (n[p] === offset) { return p; } }
    return null;
}; Date.prototype.clone = function() { return new Date(this.getTime()); }; Date.prototype.compareTo = function(date) {
    if (isNaN(this)) { throw new Error(this); }
    if (date instanceof Date && !isNaN(date)) { return (this > date) ? 1 : (this < date) ? -1 : 0; } else { throw new TypeError(date); } 
}; Date.prototype.equals = function(date) { return (this.compareTo(date) === 0); }; Date.prototype.between = function(start, end) { var t = this.getTime(); return t >= start.getTime() && t <= end.getTime(); }; Date.prototype.addMilliseconds = function(value) { this.setMilliseconds(this.getMilliseconds() + value); return this; }; Date.prototype.addSeconds = function(value) { return this.addMilliseconds(value * 1000); }; Date.prototype.addMinutes = function(value) { return this.addMilliseconds(value * 60000); }; Date.prototype.addHours = function(value) { return this.addMilliseconds(value * 3600000); }; Date.prototype.addDays = function(value) { return this.addMilliseconds(value * 86400000); }; Date.prototype.addWeeks = function(value) { return this.addMilliseconds(value * 604800000); }; Date.prototype.addMonths = function(value) { var n = this.getDate(); this.setDate(1); this.setMonth(this.getMonth() + value); this.setDate(Math.min(n, this.getDaysInMonth())); return this; }; Date.prototype.addYears = function(value) { return this.addMonths(value * 12); }; Date.prototype.add = function(config) {
    if (typeof config == "number") { this._orient = config; return this; }
    var x = config; if (x.millisecond || x.milliseconds) { this.addMilliseconds(x.millisecond || x.milliseconds); }
    if (x.second || x.seconds) { this.addSeconds(x.second || x.seconds); }
    if (x.minute || x.minutes) { this.addMinutes(x.minute || x.minutes); }
    if (x.hour || x.hours) { this.addHours(x.hour || x.hours); }
    if (x.month || x.months) { this.addMonths(x.month || x.months); }
    if (x.year || x.years) { this.addYears(x.year || x.years); }
    if (x.day || x.days) { this.addDays(x.day || x.days); }
    return this;
}; Date._validate = function(value, min, max, name) {
    if (typeof value != "number") { throw new TypeError(value + " is not a Number."); } else if (value < min || value > max) { throw new RangeError(value + " is not a valid value for " + name + "."); }
    return true;
}; Date.validateMillisecond = function(n) { return Date._validate(n, 0, 999, "milliseconds"); }; Date.validateSecond = function(n) { return Date._validate(n, 0, 59, "seconds"); }; Date.validateMinute = function(n) { return Date._validate(n, 0, 59, "minutes"); }; Date.validateHour = function(n) { return Date._validate(n, 0, 23, "hours"); }; Date.validateDay = function(n, year, month) { return Date._validate(n, 1, Date.getDaysInMonth(year, month), "days"); }; Date.validateMonth = function(n) { return Date._validate(n, 0, 11, "months"); }; Date.validateYear = function(n) { return Date._validate(n, 1, 9999, "seconds"); }; Date.prototype.set = function(config) {
    var x = config; if (!x.millisecond && x.millisecond !== 0) { x.millisecond = -1; }
    if (!x.second && x.second !== 0) { x.second = -1; }
    if (!x.minute && x.minute !== 0) { x.minute = -1; }
    if (!x.hour && x.hour !== 0) { x.hour = -1; }
    if (!x.day && x.day !== 0) { x.day = -1; }
    if (!x.month && x.month !== 0) { x.month = -1; }
    if (!x.year && x.year !== 0) { x.year = -1; }
    if (x.millisecond != -1 && Date.validateMillisecond(x.millisecond)) { this.addMilliseconds(x.millisecond - this.getMilliseconds()); }
    if (x.second != -1 && Date.validateSecond(x.second)) { this.addSeconds(x.second - this.getSeconds()); }
    if (x.minute != -1 && Date.validateMinute(x.minute)) { this.addMinutes(x.minute - this.getMinutes()); }
    if (x.hour != -1 && Date.validateHour(x.hour)) { this.addHours(x.hour - this.getHours()); }
    if (x.month !== -1 && Date.validateMonth(x.month)) { this.addMonths(x.month - this.getMonth()); }
    if (x.year != -1 && Date.validateYear(x.year)) { this.addYears(x.year - this.getFullYear()); }
    if (x.day != -1 && Date.validateDay(x.day, this.getFullYear(), this.getMonth())) { this.addDays(x.day - this.getDate()); }
    if (x.timezone) { this.setTimezone(x.timezone); }
    if (x.timezoneOffset) { this.setTimezoneOffset(x.timezoneOffset); }
    return this;
}; Date.prototype.clearTime = function() { this.setHours(0); this.setMinutes(0); this.setSeconds(0); this.setMilliseconds(0); return this; }; Date.prototype.isLeapYear = function() { var y = this.getFullYear(); return (((y % 4 === 0) && (y % 100 !== 0)) || (y % 400 === 0)); }; Date.prototype.isWeekday = function() { return !(this.is().sat() || this.is().sun()); }; Date.prototype.getDaysInMonth = function() { return Date.getDaysInMonth(this.getFullYear(), this.getMonth()); }; Date.prototype.moveToFirstDayOfMonth = function() { return this.set({ day: 1 }); }; Date.prototype.moveToLastDayOfMonth = function() { return this.set({ day: this.getDaysInMonth() }); }; Date.prototype.moveToDayOfWeek = function(day, orient) { var diff = (day - this.getDay() + 7 * (orient || +1)) % 7; return this.addDays((diff === 0) ? diff += 7 * (orient || +1) : diff); }; Date.prototype.moveToMonth = function(month, orient) { var diff = (month - this.getMonth() + 12 * (orient || +1)) % 12; return this.addMonths((diff === 0) ? diff += 12 * (orient || +1) : diff); }; Date.prototype.getDayOfYear = function() { return Math.floor((this - new Date(this.getFullYear(), 0, 1)) / 86400000); }; Date.prototype.getWeekOfYear = function(firstDayOfWeek) {
    var y = this.getFullYear(), m = this.getMonth(), d = this.getDate(); var dow = firstDayOfWeek || Date.CultureInfo.firstDayOfWeek; var offset = 7 + 1 - new Date(y, 0, 1).getDay(); if (offset == 8) { offset = 1; }
    var daynum = ((Date.UTC(y, m, d, 0, 0, 0) - Date.UTC(y, 0, 1, 0, 0, 0)) / 86400000) + 1; var w = Math.floor((daynum - offset + 7) / 7); if (w === dow) { y--; var prevOffset = 7 + 1 - new Date(y, 0, 1).getDay(); if (prevOffset == 2 || prevOffset == 8) { w = 53; } else { w = 52; } }
    return w;
}; Date.prototype.isDST = function() { console.log('isDST'); return this.toString().match(/(E|C|M|P)(S|D)T/)[2] == "D"; }; Date.prototype.getTimezone = function() { return Date.getTimezoneAbbreviation(this.getUTCOffset, this.isDST()); }; Date.prototype.setTimezoneOffset = function(s) { var here = this.getTimezoneOffset(), there = Number(s) * -6 / 10; this.addMinutes(there - here); return this; }; Date.prototype.setTimezone = function(s) { return this.setTimezoneOffset(Date.getTimezoneOffset(s)); }; Date.prototype.getUTCOffset = function() { var n = this.getTimezoneOffset() * -10 / 6, r; if (n < 0) { r = (n - 10000).toString(); return r[0] + r.substr(2); } else { r = (n + 10000).toString(); return "+" + r.substr(1); } }; Date.prototype.getDayName = function(abbrev) { return abbrev ? Date.CultureInfo.abbreviatedDayNames[this.getDay()] : Date.CultureInfo.dayNames[this.getDay()]; }; Date.prototype.getMonthName = function(abbrev) { return abbrev ? Date.CultureInfo.abbreviatedMonthNames[this.getMonth()] : Date.CultureInfo.monthNames[this.getMonth()]; }; Date.prototype._toString = Date.prototype.toString; Date.prototype.toString = function(format) { var self = this; var p = function p(s) { return (s.toString().length == 1) ? "0" + s : s; }; return format ? format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g, function(format) { switch (format) { case "hh": return p(self.getHours() < 13 ? self.getHours() : (self.getHours() - 12)); case "h": return self.getHours() < 13 ? self.getHours() : (self.getHours() - 12); case "HH": return p(self.getHours()); case "H": return self.getHours(); case "mm": return p(self.getMinutes()); case "m": return self.getMinutes(); case "ss": return p(self.getSeconds()); case "s": return self.getSeconds(); case "yyyy": return self.getFullYear(); case "yy": return self.getFullYear().toString().substring(2, 4); case "dddd": return self.getDayName(); case "ddd": return self.getDayName(true); case "dd": return p(self.getDate()); case "d": return self.getDate().toString(); case "MMMM": return self.getMonthName(); case "MMM": return self.getMonthName(true); case "MM": return p((self.getMonth() + 1)); case "M": return self.getMonth() + 1; case "t": return self.getHours() < 12 ? Date.CultureInfo.amDesignator.substring(0, 1) : Date.CultureInfo.pmDesignator.substring(0, 1); case "tt": return self.getHours() < 12 ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator; case "zzz": case "zz": case "z": return ""; } }) : this._toString(); };
Date.now = function() { return new Date(); }; Date.today = function() { return Date.now().clearTime(); }; Date.prototype._orient = +1; Date.prototype.next = function() { this._orient = +1; return this; }; Date.prototype.last = Date.prototype.prev = Date.prototype.previous = function() { this._orient = -1; return this; }; Date.prototype._is = false; Date.prototype.is = function() { this._is = true; return this; }; Number.prototype._dateElement = "day"; Number.prototype.fromNow = function() { var c = {}; c[this._dateElement] = this; return Date.now().add(c); }; Number.prototype.ago = function() { var c = {}; c[this._dateElement] = this * -1; return Date.now().add(c); }; (function() {
    var $D = Date.prototype, $N = Number.prototype; var dx = ("sunday monday tuesday wednesday thursday friday saturday").split(/\s/), mx = ("january february march april may june july august september october november december").split(/\s/), px = ("Millisecond Second Minute Hour Day Week Month Year").split(/\s/), de; var df = function(n) {
        return function() {
            if (this._is) { this._is = false; return this.getDay() == n; }
            return this.moveToDayOfWeek(n, this._orient);
        };
    }; for (var i = 0; i < dx.length; i++) { $D[dx[i]] = $D[dx[i].substring(0, 3)] = df(i); }
    var mf = function(n) {
        return function() {
            if (this._is) { this._is = false; return this.getMonth() === n; }
            return this.moveToMonth(n, this._orient);
        };
    }; for (var j = 0; j < mx.length; j++) { $D[mx[j]] = $D[mx[j].substring(0, 3)] = mf(j); }
    var ef = function(j) {
        return function() {
            if (j.substring(j.length - 1) != "s") { j += "s"; }
            return this["add" + j](this._orient);
        };
    }; var nf = function(n) { return function() { this._dateElement = n; return this; }; }; for (var k = 0; k < px.length; k++) { de = px[k].toLowerCase(); $D[de] = $D[de + "s"] = ef(px[k]); $N[de] = $N[de + "s"] = nf(de); } 
} ()); Date.prototype.toJSONString = function() { return this.toString("yyyy-MM-ddThh:mm:ssZ"); }; Date.prototype.toShortDateString = function() { return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern); }; Date.prototype.toLongDateString = function() { return this.toString(Date.CultureInfo.formatPatterns.longDatePattern); }; Date.prototype.toShortTimeString = function() { return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern); }; Date.prototype.toLongTimeString = function() { return this.toString(Date.CultureInfo.formatPatterns.longTimePattern); }; Date.prototype.getOrdinal = function() { switch (this.getDate()) { case 1: case 21: case 31: return "st"; case 2: case 22: return "nd"; case 3: case 23: return "rd"; default: return "th"; } };
(function() {
    Date.Parsing = { Exception: function(s) { this.message = "Parse error at '" + s.substring(0, 10) + " ...'"; } }; var $P = Date.Parsing; var _ = $P.Operators = { rtoken: function(r) { return function(s) { var mx = s.match(r); if (mx) { return ([mx[0], s.substring(mx[0].length)]); } else { throw new $P.Exception(s); } }; }, token: function(s) { return function(s) { return _.rtoken(new RegExp("^\s*" + s + "\s*"))(s); }; }, stoken: function(s) { return _.rtoken(new RegExp("^" + s)); }, until: function(p) {
        return function(s) {
            var qx = [], rx = null; while (s.length) {
                try { rx = p.call(this, s); } catch (e) { qx.push(rx[0]); s = rx[1]; continue; }
                break;
            }
            return [qx, s];
        };
    }, many: function(p) {
        return function(s) {
            var rx = [], r = null; while (s.length) {
                try { r = p.call(this, s); } catch (e) { return [rx, s]; }
                rx.push(r[0]); s = r[1];
            }
            return [rx, s];
        };
    }, optional: function(p) {
        return function(s) {
            var r = null; try { r = p.call(this, s); } catch (e) { return [null, s]; }
            return [r[0], r[1]];
        };
    }, not: function(p) {
        return function(s) {
            try { p.call(this, s); } catch (e) { return [null, s]; }
            throw new $P.Exception(s);
        };
    }, ignore: function(p) { return p ? function(s) { var r = null; r = p.call(this, s); return [null, r[1]]; } : null; }, product: function() {


        var px = arguments[0], qx = Array.prototype.slice.call(arguments, 1), rx = []; for (var i = 0; i < px.length; i++) { rx.push(_.each(px[i], qx)); }
        return rx;
    }, cache: function(rule) {
        var cache = {}, r = null; return function(s) {
            try { r = cache[s] = (cache[s] || rule.call(this, s)); } catch (e) { r = cache[s] = e; }
            if (r instanceof $P.Exception) { throw r; } else { return r; } 
        };
    }, any: function() {
        var px = arguments; return function(s) {
            var r = null; for (var i = 0; i < px.length; i++) {
                if (px[i] == null) { continue; }
                try { r = (px[i].call(this, s)); } catch (e) { r = null; }
                if (r) { return r; } 
            }
            throw new $P.Exception(s);
        };
    }, each: function() {
        var px = arguments; return function(s) {
            var rx = [], r = null; for (var i = 0; i < px.length; i++) {
                if (px[i] == null) { continue; }
                try { r = (px[i].call(this, s)); } catch (e) { throw new $P.Exception(s); }
                rx.push(r[0]); s = r[1];
            }
            return [rx, s];
        };
    }, all: function() { var px = arguments, _ = _; return _.each(_.optional(px)); }, sequence: function(px, d, c) {
        d = d || _.rtoken(/^\s*/); c = c || null; if (px.length == 1) { return px[0]; }
        return function(s) {
            var r = null, q = null; var rx = []; for (var i = 0; i < px.length; i++) {
                try { r = px[i].call(this, s); } catch (e) { break; }
                rx.push(r[0]); try { q = d.call(this, r[1]); } catch (ex) { q = null; break; }
                s = q[1];
            }
            if (!r) { throw new $P.Exception(s); }
            if (q) { throw new $P.Exception(q[1]); }
            if (c) { try { r = c.call(this, r[1]); } catch (ey) { throw new $P.Exception(r[1]); } }
            return [rx, (r ? r[1] : s)];
        };
    }, between: function(d1, p, d2) { d2 = d2 || d1; var _fn = _.each(_.ignore(d1), p, _.ignore(d2)); return function(s) { var rx = _fn.call(this, s); return [[rx[0][0], r[0][2]], rx[1]]; }; }, list: function(p, d, c) { d = d || _.rtoken(/^\s*/); c = c || null; return (p instanceof Array ? _.each(_.product(p.slice(0, -1), _.ignore(d)), p.slice(-1), _.ignore(c)) : _.each(_.many(_.each(p, _.ignore(d))), px, _.ignore(c))); }, set: function(px, d, c) {
        d = d || _.rtoken(/^\s*/); c = c || null; return function(s) {
            var r = null, p = null, q = null, rx = null, best = [[], s], last = false; for (var i = 0; i < px.length; i++) {
                q = null; p = null; r = null; last = (px.length == 1); try { r = px[i].call(this, s); } catch (e) { continue; }
                rx = [[r[0]], r[1]]; if (r[1].length > 0 && !last) { try { q = d.call(this, r[1]); } catch (ex) { last = true; } } else { last = true; }
                if (!last && q[1].length === 0) { last = true; }
                if (!last) {
                    var qx = []; for (var j = 0; j < px.length; j++) { if (i != j) { qx.push(px[j]); } }
                    p = _.set(qx, d).call(this, q[1]); if (p[0].length > 0) { rx[0] = rx[0].concat(p[0]); rx[1] = p[1]; } 
                }
                if (rx[1].length < best[1].length) { best = rx; }
                if (best[1].length === 0) { break; } 
            }
            if (best[0].length === 0) { return best; }
            if (c) {
                try { q = c.call(this, best[1]); } catch (ey) { throw new $P.Exception(best[1]); }
                best[1] = q[1];
            }
            return best;
        };
    }, forward: function(gr, fname) { return function(s) { return gr[fname].call(this, s); }; }, replace: function(rule, repl) { return function(s) { var r = rule.call(this, s); return [repl, r[1]]; }; }, process: function(rule, fn) { return function(s) { var r = rule.call(this, s); return [fn.call(this, r[0]), r[1]]; }; }, min: function(min, rule) {
        return function(s) {
            var rx = rule.call(this, s); if (rx[0].length < min) { throw new $P.Exception(s); }
            return rx;
        };
    } 
    }; var _generator = function(op) {
        return function() {
            var args = null, rx = []; if (arguments.length > 1) { args = Array.prototype.slice.call(arguments); } else if (arguments[0] instanceof Array) { args = arguments[0]; }
            if (args) { for (var i = 0, px = args.shift(); i < px.length; i++) { args.unshift(px[i]); rx.push(op.apply(null, args)); args.shift(); return rx; } } else { return op.apply(null, arguments); } 
        };
    }; var gx = "optional not ignore cache".split(/\s/); for (var i = 0; i < gx.length; i++) { _[gx[i]] = _generator(_[gx[i]]); }
    var _vector = function(op) { return function() { if (arguments[0] instanceof Array) { return op.apply(null, arguments[0]); } else { return op.apply(null, arguments); } }; }; var vx = "each any all".split(/\s/); for (var j = 0; j < vx.length; j++) { _[vx[j]] = _vector(_[vx[j]]); } 
} ()); (function() {
    var flattenAndCompact = function(ax) {
        var rx = []; for (var i = 0; i < ax.length; i++) { if (ax[i] instanceof Array) { rx = rx.concat(flattenAndCompact(ax[i])); } else { if (ax[i]) { rx.push(ax[i]); } } }
        return rx;
    }; Date.Grammar = {}; Date.Translator = { hour: function(s) { return function() { this.hour = Number(s); }; }, minute: function(s) { return function() { this.minute = Number(s); }; }, second: function(s) { return function() { this.second = Number(s); }; }, meridian: function(s) { return function() { this.meridian = s.slice(0, 1).toLowerCase(); }; }, timezone: function(s) { return function() { var n = s.replace(/[^\d\+\-]/g, ""); if (n.length) { this.timezoneOffset = Number(n); } else { this.timezone = s.toLowerCase(); } }; }, day: function(x) { var s = x[0]; return function() { this.day = Number(s.match(/\d+/)[0]); }; }, month: function(s) { return function() { this.month = ((s.length == 3) ? Date.getMonthNumberFromName(s) : (Number(s) - 1)); }; }, year: function(s) { return function() { var n = Number(s); this.year = ((s.length > 2) ? n : (n + (((n + 2000) < Date.CultureInfo.twoDigitYearMax) ? 2000 : 1900))); }; }, rday: function(s) { return function() { switch (s) { case "yesterday": this.days = -1; break; case "tomorrow": this.days = 1; break; case "today": this.days = 0; break; case "now": this.days = 0; this.now = true; break; } }; }, finishExact: function(x) {
        x = (x instanceof Array) ? x : [x]; var now = new Date(); this.year = now.getFullYear(); this.month = now.getMonth(); this.day = 1; this.hour = 0; this.minute = 0; this.second = 0; for (var i = 0; i < x.length; i++) { if (x[i]) { x[i].call(this); } }
        this.hour = (this.meridian == "p" && this.hour < 13) ? this.hour + 12 : this.hour; if (this.day > Date.getDaysInMonth(this.year, this.month)) { throw new RangeError(this.day + " is not a valid value for days."); }
        var r = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second); if (this.timezone) { r.set({ timezone: this.timezone }); } else if (this.timezoneOffset) { r.set({ timezoneOffset: this.timezoneOffset }); }
        return r;
    }, finish: function(x) {
        x = (x instanceof Array) ? flattenAndCompact(x) : [x]; if (x.length === 0) { return null; }
        for (var i = 0; i < x.length; i++) { if (typeof x[i] == "function") { x[i].call(this); } }
        if (this.now) { return new Date(); }
        var today = Date.today(); var method = null; var expression = !!(this.days != null || this.orient || this.operator); if (expression) {
            var gap, mod, orient; orient = ((this.orient == "past" || this.operator == "subtract") ? -1 : 1); if (this.weekday) { this.unit = "day"; gap = (Date.getDayNumberFromName(this.weekday) - today.getDay()); mod = 7; this.days = gap ? ((gap + (orient * mod)) % mod) : (orient * mod); }
            if (this.month) { this.unit = "month"; gap = (this.month - today.getMonth()); mod = 12; this.months = gap ? ((gap + (orient * mod)) % mod) : (orient * mod); this.month = null; }
            if (!this.unit) { this.unit = "day"; }
            if (this[this.unit + "s"] == null || this.operator != null) {
                if (!this.value) { this.value = 1; }
                if (this.unit == "week") { this.unit = "day"; this.value = this.value * 7; }
                this[this.unit + "s"] = this.value * orient;
            }
            return today.add(this);
        } else {
            if (this.meridian && this.hour) { this.hour = (this.hour < 13 && this.meridian == "p") ? this.hour + 12 : this.hour; }
            if (this.weekday && !this.day) { this.day = (today.addDays((Date.getDayNumberFromName(this.weekday) - today.getDay()))).getDate(); }
            if (this.month && !this.day) { this.day = 1; }
            return today.set(this);
        } 
    } 
    }; var _ = Date.Parsing.Operators, g = Date.Grammar, t = Date.Translator, _fn; g.datePartDelimiter = _.rtoken(/^([\s\-\.\,\/\x27]+)/); g.timePartDelimiter = _.stoken(":"); g.whiteSpace = _.rtoken(/^\s*/); g.generalDelimiter = _.rtoken(/^(([\s\,]|at|on)+)/); var _C = {}; g.ctoken = function(keys) {
        var fn = _C[keys]; if (!fn) {
            var c = Date.CultureInfo.regexPatterns; var kx = keys.split(/\s+/), px = []; for (var i = 0; i < kx.length; i++) { px.push(_.replace(_.rtoken(c[kx[i]]), kx[i])); }
            fn = _C[keys] = _.any.apply(null, px);
        }
        return fn;
    }; g.ctoken2 = function(key) { return _.rtoken(Date.CultureInfo.regexPatterns[key]); }; g.h = _.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/), t.hour)); g.hh = _.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/), t.hour)); g.H = _.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/), t.hour)); g.HH = _.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/), t.hour)); g.m = _.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/), t.minute)); g.mm = _.cache(_.process(_.rtoken(/^[0-5][0-9]/), t.minute)); g.s = _.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/), t.second)); g.ss = _.cache(_.process(_.rtoken(/^[0-5][0-9]/), t.second)); g.hms = _.cache(_.sequence([g.H, g.mm, g.ss], g.timePartDelimiter)); g.t = _.cache(_.process(g.ctoken2("shortMeridian"), t.meridian)); g.tt = _.cache(_.process(g.ctoken2("longMeridian"), t.meridian)); g.z = _.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/), t.timezone)); g.zz = _.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/), t.timezone)); g.zzz = _.cache(_.process(g.ctoken2("timezone"), t.timezone)); g.timeSuffix = _.each(_.ignore(g.whiteSpace), _.set([g.tt, g.zzz])); g.time = _.each(_.optional(_.ignore(_.stoken("T"))), g.hms, g.timeSuffix); g.d = _.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/), _.optional(g.ctoken2("ordinalSuffix"))), t.day)); g.dd = _.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/), _.optional(g.ctoken2("ordinalSuffix"))), t.day)); g.ddd = g.dddd = _.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"), function(s) { return function() { this.weekday = s; }; })); g.M = _.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/), t.month)); g.MM = _.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/), t.month)); g.MMM = g.MMMM = _.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"), t.month)); g.y = _.cache(_.process(_.rtoken(/^(\d\d?)/), t.year)); g.yy = _.cache(_.process(_.rtoken(/^(\d\d)/), t.year)); g.yyy = _.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/), t.year)); g.yyyy = _.cache(_.process(_.rtoken(/^(\d\d\d\d)/), t.year)); _fn = function() { return _.each(_.any.apply(null, arguments), _.not(g.ctoken2("timeContext"))); }; g.day = _fn(g.d, g.dd); g.month = _fn(g.M, g.MMM); g.year = _fn(g.yyyy, g.yy); g.orientation = _.process(g.ctoken("past future"), function(s) { return function() { this.orient = s; }; }); g.operator = _.process(g.ctoken("add subtract"), function(s) { return function() { this.operator = s; }; }); g.rday = _.process(g.ctoken("yesterday tomorrow today now"), t.rday); g.unit = _.process(g.ctoken("minute hour day week month year"), function(s) { return function() { this.unit = s; }; }); g.value = _.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/), function(s) { return function() { this.value = s.replace(/\D/g, ""); }; }); g.expression = _.set([g.rday, g.operator, g.value, g.unit, g.orientation, g.ddd, g.MMM]); _fn = function() { return _.set(arguments, g.datePartDelimiter); }; g.mdy = _fn(g.ddd, g.month, g.day, g.year); g.ymd = _fn(g.ddd, g.year, g.month, g.day); g.dmy = _fn(g.ddd, g.day, g.month, g.year); g.date = function(s) { return ((g[Date.CultureInfo.dateElementOrder] || g.mdy).call(this, s)); }; g.format = _.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/), function(fmt) { if (g[fmt]) { return g[fmt]; } else { throw Date.Parsing.Exception(fmt); } }), _.process(_.rtoken(/^[^dMyhHmstz]+/), function(s) { return _.ignore(_.stoken(s)); }))), function(rules) { return _.process(_.each.apply(null, rules), t.finishExact); }); var _F = {}; var _get = function(f) { return _F[f] = (_F[f] || g.format(f)[0]); }; g.formats = function(fx) {
        if (fx instanceof Array) {
            var rx = []; for (var i = 0; i < fx.length; i++) { rx.push(_get(fx[i])); }
            return _.any.apply(null, rx);
        } else { return _get(fx); } 
    }; g._formats = g.formats(["yyyy-MM-ddTHH:mm:ss", "ddd, MMM dd, yyyy H:mm:ss tt", "ddd MMM d yyyy HH:mm:ss zzz", "d"]); g._start = _.process(_.set([g.date, g.time, g.expression], g.generalDelimiter, g.whiteSpace), t.finish); g.start = function(s) {
        try { var r = g._formats.call({}, s); if (r[1].length === 0) { return r; } } catch (e) { }
        return g._start.call({}, s);
    };
} ()); Date._parse = Date.parse; Date.parse = function(s) {
    var r = null; if (!s) { return null; }
    try { r = Date.Grammar.start.call({}, s); } catch (e) { return null; }
    return ((r[1].length === 0) ? r[0] : null);
}; Date.getParseFunction = function(fx) {
    var fn = Date.Grammar.formats(fx); return function(s) {
        var r = null; try { r = fn.call({}, s); } catch (e) { return null; }
        return ((r[1].length === 0) ? r[0] : null);
    };
}; Date.parseExact = function(s, fx) { return Date.getParseFunction(fx)(s); };



/*
* jQuery Cycle Plugin (with Transition Definitions)
* Examples and documentation at: http://jquery.malsup.com/cycle/
* Copyright (c) 2007-2009 M. Alsup
* Version: 2.56 (03-MAR-2009)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
* Requires: jQuery v1.2.3 or later
*
* Originally based on the work of:
*	1) Matt Oakes (http://portfolio.gizone.co.uk/applications/slideshow/)
*	2) Torsten Baldes (http://medienfreunde.com/lab/innerfade/)
*	3) Benjamin Sterling (http://www.benjaminsterling.com/experiments/jqShuffle/)
*/
(function($) { var ver = "2.56"; if ($.support == undefined) { $.support = { opacity: !($.browser.msie) }; } function log() { if (window.console && window.console.log) { window.console.log("[cycle] " + Array.prototype.join.call(arguments, "")); } } $.fn.cycle = function(options, arg2) { if (this.length == 0 && options != "stop") { if (!$.isReady && this.selector) { log("DOM not ready, queuing slideshow"); var o = { s: this.selector, c: this.context }; $(function() { $(o.s, o.c).cycle(options, arg2); }); return this; } log("terminating; zero elements found by selector" + ($.isReady ? "" : " (DOM not ready)")); return this; } return this.each(function() { options = handleArguments(this, options, arg2); if (options === false) { return; } if (this.cycleTimeout) { clearTimeout(this.cycleTimeout); } this.cycleTimeout = this.cyclePause = 0; var $cont = $(this); var $slides = options.slideExpr ? $(options.slideExpr, this) : $cont.children(); var els = $slides.get(); if (els.length < 2) { log("terminating; too few slides: " + els.length); return; } var opts = buildOptions($cont, $slides, els, options); if (opts === false) { return; } if (opts.timeout || opts.continuous) { this.cycleTimeout = setTimeout(function() { go(els, opts, 0, !opts.rev); }, opts.continuous ? 10 : opts.timeout + (opts.delay || 0)); } }); }; function handleArguments(cont, options, arg2) { if (cont.cycleStop == undefined) { cont.cycleStop = 0; } if (options === undefined || options === null) { options = {}; } if (options.constructor == String) { switch (options) { case "stop": cont.cycleStop++; if (cont.cycleTimeout) { clearTimeout(cont.cycleTimeout); } cont.cycleTimeout = 0; $(cont).removeData("cycle.opts"); return false; case "pause": cont.cyclePause = 1; return false; case "resume": cont.cyclePause = 0; if (arg2 === true) { options = $(cont).data("cycle.opts"); if (!options) { log("options not found, can not resume"); return; } if (cont.cycleTimeout) { clearTimeout(cont.cycleTimeout); cont.cycleTimeout = 0; } go(options.elements, options, 1, 1); } return false; default: options = { fx: options }; } } else { if (options.constructor == Number) { var num = options; options = $(cont).data("cycle.opts"); if (!options) { log("options not found, can not advance slide"); return false; } if (num < 0 || num >= options.elements.length) { log("invalid slide index: " + num); return false; } options.nextSlide = num; if (cont.cycleTimeout) { clearTimeout(this.cycleTimeout); cont.cycleTimeout = 0; } if (typeof arg2 == "string") { options.oneTimeFx = arg2; } go(options.elements, options, 1, num >= options.currSlide); return false; } } return options; } function buildOptions($cont, $slides, els, options) { var opts = $.extend({}, $.fn.cycle.defaults, options || {}, $.metadata ? $cont.metadata() : $.meta ? $cont.data() : {}); if (opts.autostop) { opts.countdown = opts.autostopCount || els.length; } var cont = $cont[0]; $cont.data("cycle.opts", opts); opts.$cont = $cont; opts.stopCount = cont.cycleStop; opts.elements = els; opts.before = opts.before ? [opts.before] : []; opts.after = opts.after ? [opts.after] : []; opts.after.unshift(function() { opts.busy = 0; }); if (!$.support.opacity && opts.cleartype) { opts.after.push(function() { this.style.removeAttribute("filter"); }); } if (opts.continuous) { opts.after.push(function() { go(els, opts, 0, !opts.rev); }); } saveOriginalOpts(opts); if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg) { clearTypeFix($slides); } if ($cont.css("position") == "static") { $cont.css("position", "relative"); } if (opts.width) { $cont.width(opts.width); } if (opts.height && opts.height != "auto") { $cont.height(opts.height); } if (opts.startingSlide) { opts.startingSlide = parseInt(opts.startingSlide); } if (opts.random) { opts.randomMap = []; for (var i = 0; i < els.length; i++) { opts.randomMap.push(i); } opts.randomMap.sort(function(a, b) { return Math.random() - 0.5; }); opts.randomIndex = 0; opts.startingSlide = opts.randomMap[0]; } else { if (opts.startingSlide >= els.length) { opts.startingSlide = 0; } } opts.currSlide = opts.startingSlide = opts.startingSlide || 0; var first = opts.startingSlide; $slides.css({ position: "absolute", top: 0, left: 0 }).hide().each(function(i) { var z = first ? i >= first ? els.length - (i - first) : first - i : els.length - i; $(this).css("z-index", z); }); $(els[first]).css("opacity", 1).show(); if (!$.support.opacity && opts.cleartype) { els[first].style.removeAttribute("filter"); } if (opts.fit && opts.width) { $slides.width(opts.width); } if (opts.fit && opts.height && opts.height != "auto") { $slides.height(opts.height); } var reshape = opts.containerResize && !$cont.innerHeight(); if (reshape) { var maxw = 0, maxh = 0; for (var i = 0; i < els.length; i++) { var $e = $(els[i]), e = $e[0], w = $e.outerWidth(), h = $e.outerHeight(); if (!w) { w = e.offsetWidth; } if (!h) { h = e.offsetHeight; } maxw = w > maxw ? w : maxw; maxh = h > maxh ? h : maxh; } if (maxw > 0 && maxh > 0) { $cont.css({ width: maxw + "px", height: maxh + "px" }); } } if (opts.pause) { $cont.hover(function() { this.cyclePause++; }, function() { this.cyclePause--; }); } supportMultiTransitions(opts); if (!opts.multiFx) { var init = $.fn.cycle.transitions[opts.fx]; if ($.isFunction(init)) { init($cont, $slides, opts); } else { if (opts.fx != "custom" && !opts.multiFx) { log("unknown transition: " + opts.fx, "; slideshow terminating"); return false; } } } $slides.each(function() { var $el = $(this); this.cycleH = (opts.fit && opts.height) ? opts.height : $el.height(); this.cycleW = (opts.fit && opts.width) ? opts.width : $el.width(); }); opts.cssBefore = opts.cssBefore || {}; opts.animIn = opts.animIn || {}; opts.animOut = opts.animOut || {}; $slides.not(":eq(" + first + ")").css(opts.cssBefore); if (opts.cssFirst) { $($slides[first]).css(opts.cssFirst); } if (opts.timeout) { opts.timeout = parseInt(opts.timeout); if (opts.speed.constructor == String) { opts.speed = $.fx.speeds[opts.speed] || parseInt(opts.speed); } if (!opts.sync) { opts.speed = opts.speed / 2; } while ((opts.timeout - opts.speed) < 250) { opts.timeout += opts.speed; } } if (opts.easing) { opts.easeIn = opts.easeOut = opts.easing; } if (!opts.speedIn) { opts.speedIn = opts.speed; } if (!opts.speedOut) { opts.speedOut = opts.speed; } opts.slideCount = els.length; opts.currSlide = opts.lastSlide = first; if (opts.random) { opts.nextSlide = opts.currSlide; if (++opts.randomIndex == els.length) { opts.randomIndex = 0; } opts.nextSlide = opts.randomMap[opts.randomIndex]; } else { opts.nextSlide = opts.startingSlide >= (els.length - 1) ? 0 : opts.startingSlide + 1; } var e0 = $slides[first]; if (opts.before.length) { opts.before[0].apply(e0, [e0, e0, opts, true]); } if (opts.after.length > 1) { opts.after[1].apply(e0, [e0, e0, opts, true]); } if (opts.next) { $(opts.next).click(function() { return advance(opts, opts.rev ? -1 : 1); }); } if (opts.prev) { $(opts.prev).click(function() { return advance(opts, opts.rev ? 1 : -1); }); } if (opts.pager) { buildPager(els, opts); } exposeAddSlide(opts, els); return opts; } function saveOriginalOpts(opts) { opts.original = { before: [], after: [] }; opts.original.cssBefore = $.extend({}, opts.cssBefore); opts.original.cssAfter = $.extend({}, opts.cssAfter); opts.original.animIn = $.extend({}, opts.animIn); opts.original.animOut = $.extend({}, opts.animOut); $.each(opts.before, function() { opts.original.before.push(this); }); $.each(opts.after, function() { opts.original.after.push(this); }); } function supportMultiTransitions(opts) { var txs = $.fn.cycle.transitions; if (opts.fx.indexOf(",") > 0) { opts.multiFx = true; opts.fxs = opts.fx.replace(/\s*/g, "").split(","); for (var i = 0; i < opts.fxs.length; i++) { var fx = opts.fxs[i]; var tx = txs[fx]; if (!tx || !txs.hasOwnProperty(fx) || !$.isFunction(tx)) { log("discarding unknowtn transition: ", fx); opts.fxs.splice(i, 1); i--; } } if (!opts.fxs.length) { log("No valid transitions named; slideshow terminating."); return false; } } else { if (opts.fx == "all") { opts.multiFx = true; opts.fxs = []; for (p in txs) { var tx = txs[p]; if (txs.hasOwnProperty(p) && $.isFunction(tx)) { opts.fxs.push(p); } } } } if (opts.multiFx && opts.randomizeEffects) { var r1 = Math.floor(Math.random() * 20) + 30; for (var i = 0; i < r1; i++) { var r2 = Math.floor(Math.random() * opts.fxs.length); opts.fxs.push(opts.fxs.splice(r2, 1)[0]); } log("randomized fx sequence: ", opts.fxs); } } function exposeAddSlide(opts, els) { opts.addSlide = function(newSlide, prepend) { var $s = $(newSlide), s = $s[0]; if (!opts.autostopCount) { opts.countdown++; } els[prepend ? "unshift" : "push"](s); if (opts.els) { opts.els[prepend ? "unshift" : "push"](s); } opts.slideCount = els.length; $s.css("position", "absolute"); $s[prepend ? "prependTo" : "appendTo"](opts.$cont); if (prepend) { opts.currSlide++; opts.nextSlide++; } if (!$.support.opacity && opts.cleartype && !opts.cleartypeNoBg) { clearTypeFix($s); } if (opts.fit && opts.width) { $s.width(opts.width); } if (opts.fit && opts.height && opts.height != "auto") { $slides.height(opts.height); } s.cycleH = (opts.fit && opts.height) ? opts.height : $s.height(); s.cycleW = (opts.fit && opts.width) ? opts.width : $s.width(); $s.css(opts.cssBefore); if (opts.pager) { $.fn.cycle.createPagerAnchor(els.length - 1, s, $(opts.pager), els, opts); } if ($.isFunction(opts.onAddSlide)) { opts.onAddSlide($s); } else { $s.hide(); } }; } $.fn.cycle.resetState = function(opts, fx) { var fx = fx || opts.fx; opts.before = []; opts.after = []; opts.cssBefore = $.extend({}, opts.original.cssBefore); opts.cssAfter = $.extend({}, opts.original.cssAfter); opts.animIn = $.extend({}, opts.original.animIn); opts.animOut = $.extend({}, opts.original.animOut); opts.fxFn = null; $.each(opts.original.before, function() { opts.before.push(this); }); $.each(opts.original.after, function() { opts.after.push(this); }); var init = $.fn.cycle.transitions[fx]; if ($.isFunction(init)) { init(opts.$cont, $(opts.elements), opts); } }; function go(els, opts, manual, fwd) { if (manual && opts.busy) { $(els).stop(true, true); opts.busy = false; } if (opts.busy) { return; } var p = opts.$cont[0], curr = els[opts.currSlide], next = els[opts.nextSlide]; if (p.cycleStop != opts.stopCount || p.cycleTimeout === 0 && !manual) { return; } if (!manual && !p.cyclePause && ((opts.autostop && (--opts.countdown <= 0)) || (opts.nowrap && !opts.random && opts.nextSlide < opts.currSlide))) { if (opts.end) { opts.end(opts); } return; } if (manual || !p.cyclePause) { var fx = opts.fx; curr.cycleH = curr.cycleH || curr.offsetHeight; curr.cycleW = curr.cycleW || curr.offsetWidth; next.cycleH = next.cycleH || next.offsetHeight; next.cycleW = next.cycleW || next.offsetWidth; if (opts.multiFx) { if (opts.lastFx == undefined || ++opts.lastFx >= opts.fxs.length) { opts.lastFx = 0; } fx = opts.fxs[opts.lastFx]; opts.currFx = fx; } if (opts.oneTimeFx) { fx = opts.oneTimeFx; opts.oneTimeFx = null; } $.fn.cycle.resetState(opts, fx); if (opts.before.length) { $.each(opts.before, function(i, o) { if (p.cycleStop != opts.stopCount) { return; } o.apply(next, [curr, next, opts, fwd]); }); } var after = function() { $.each(opts.after, function(i, o) { if (p.cycleStop != opts.stopCount) { return; } o.apply(next, [curr, next, opts, fwd]); }); }; if (opts.nextSlide != opts.currSlide) { opts.busy = 1; if (opts.fxFn) { opts.fxFn(curr, next, opts, after, fwd); } else { if ($.isFunction($.fn.cycle[opts.fx])) { $.fn.cycle[opts.fx](curr, next, opts, after); } else { $.fn.cycle.custom(curr, next, opts, after, manual && opts.fastOnEvent); } } } opts.lastSlide = opts.currSlide; if (opts.random) { opts.currSlide = opts.nextSlide; if (++opts.randomIndex == els.length) { opts.randomIndex = 0; } opts.nextSlide = opts.randomMap[opts.randomIndex]; } else { var roll = (opts.nextSlide + 1) == els.length; opts.nextSlide = roll ? 0 : opts.nextSlide + 1; opts.currSlide = roll ? els.length - 1 : opts.nextSlide - 1; } if (opts.pager) { $.fn.cycle.updateActivePagerLink(opts.pager, opts.currSlide); } } var ms = 0; if (opts.timeout && !opts.continuous) { ms = getTimeout(curr, next, opts, fwd); } else { if (opts.continuous && p.cyclePause) { ms = 10; } } if (ms > 0) { p.cycleTimeout = setTimeout(function() { go(els, opts, 0, !opts.rev); }, ms); } } $.fn.cycle.updateActivePagerLink = function(pager, currSlide) { $(pager).find("a").removeClass("activeSlide").filter("a:eq(" + currSlide + ")").addClass("activeSlide"); }; function getTimeout(curr, next, opts, fwd) { if (opts.timeoutFn) { var t = opts.timeoutFn(curr, next, opts, fwd); if (t !== false) { return t; } } return opts.timeout; } $.fn.cycle.next = function(opts) { advance(opts, opts.rev ? -1 : 1); }; $.fn.cycle.prev = function(opts) { advance(opts, opts.rev ? 1 : -1); }; function advance(opts, val) { var els = opts.elements; var p = opts.$cont[0], timeout = p.cycleTimeout; if (timeout) { clearTimeout(timeout); p.cycleTimeout = 0; } if (opts.random && val < 0) { opts.randomIndex--; if (--opts.randomIndex == -2) { opts.randomIndex = els.length - 2; } else { if (opts.randomIndex == -1) { opts.randomIndex = els.length - 1; } } opts.nextSlide = opts.randomMap[opts.randomIndex]; } else { if (opts.random) { if (++opts.randomIndex == els.length) { opts.randomIndex = 0; } opts.nextSlide = opts.randomMap[opts.randomIndex]; } else { opts.nextSlide = opts.currSlide + val; if (opts.nextSlide < 0) { if (opts.nowrap) { return false; } opts.nextSlide = els.length - 1; } else { if (opts.nextSlide >= els.length) { if (opts.nowrap) { return false; } opts.nextSlide = 0; } } } } if ($.isFunction(opts.prevNextClick)) { opts.prevNextClick(val > 0, opts.nextSlide, els[opts.nextSlide]); } go(els, opts, 1, val >= 0); return false; } function buildPager(els, opts) { var $p = $(opts.pager); $.each(els, function(i, o) { $.fn.cycle.createPagerAnchor(i, o, $p, els, opts); }); $.fn.cycle.updateActivePagerLink(opts.pager, opts.startingSlide); } $.fn.cycle.createPagerAnchor = function(i, el, $p, els, opts) { var a = ($.isFunction(opts.pagerAnchorBuilder)) ? opts.pagerAnchorBuilder(i, el) : '<a href="#">' + (i + 1) + "</a>"; if (!a) { return; } var $a = $(a); if ($a.parents("body").length == 0) { $a.appendTo($p); } $a.bind(opts.pagerEvent, function() { opts.nextSlide = i; var p = opts.$cont[0], timeout = p.cycleTimeout; if (timeout) { clearTimeout(timeout); p.cycleTimeout = 0; } if ($.isFunction(opts.pagerClick)) { opts.pagerClick(opts.nextSlide, els[opts.nextSlide]); } go(els, opts, 1, opts.currSlide < i); return false; }); if (opts.pauseOnPagerHover) { $a.hover(function() { opts.$cont[0].cyclePause++; }, function() { opts.$cont[0].cyclePause--; }); } }; $.fn.cycle.hopsFromLast = function(opts, fwd) { var hops, l = opts.lastSlide, c = opts.currSlide; if (fwd) { hops = c > l ? c - l : opts.slideCount - l; } else { hops = c < l ? l - c : l + opts.slideCount - c; } return hops; }; function clearTypeFix($slides) { function hex(s) { var s = parseInt(s).toString(16); return s.length < 2 ? "0" + s : s; } function getBg(e) { for (; e && e.nodeName.toLowerCase() != "html"; e = e.parentNode) { var v = $.css(e, "background-color"); if (v.indexOf("rgb") >= 0) { var rgb = v.match(/\d+/g); return "#" + hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]); } if (v && v != "transparent") { return v; } } return "#ffffff"; } $slides.each(function() { $(this).css("background-color", getBg(this)); }); } $.fn.cycle.commonReset = function(curr, next, opts, w, h, rev) { $(opts.elements).not(curr).hide(); opts.cssBefore.opacity = 1; opts.cssBefore.display = "block"; if (w !== false && next.cycleW > 0) { opts.cssBefore.width = next.cycleW; } if (h !== false && next.cycleH > 0) { opts.cssBefore.height = next.cycleH; } opts.cssAfter = opts.cssAfter || {}; opts.cssAfter.display = "none"; $(curr).css("zIndex", opts.slideCount + (rev === true ? 1 : 0)); $(next).css("zIndex", opts.slideCount + (rev === true ? 0 : 1)); }; $.fn.cycle.custom = function(curr, next, opts, cb, speedOverride) { var $l = $(curr), $n = $(next); var speedIn = opts.speedIn, speedOut = opts.speedOut, easeIn = opts.easeIn, easeOut = opts.easeOut; $n.css(opts.cssBefore); if (speedOverride) { if (typeof speedOverride == "number") { speedIn = speedOut = speedOverride; } else { speedIn = speedOut = 1; } easeIn = easeOut = null; } var fn = function() { $n.animate(opts.animIn, speedIn, easeIn, cb); }; $l.animate(opts.animOut, speedOut, easeOut, function() { if (opts.cssAfter) { $l.css(opts.cssAfter); } if (!opts.sync) { fn(); } }); if (opts.sync) { fn(); } }; $.fn.cycle.transitions = { fade: function($cont, $slides, opts) { $slides.not(":eq(" + opts.currSlide + ")").css("opacity", 0); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts); opts.cssBefore.opacity = 0; }); opts.animIn = { opacity: 1 }; opts.animOut = { opacity: 0 }; opts.cssBefore = { top: 0, left: 0 }; } }; $.fn.cycle.ver = function() { return ver; }; $.fn.cycle.defaults = { fx: "fade", timeout: 4000, timeoutFn: null, continuous: 0, speed: 1000, speedIn: null, speedOut: null, next: null, prev: null, prevNextClick: null, pager: null, pagerClick: null, pagerEvent: "click", pagerAnchorBuilder: null, before: null, after: null, end: null, easing: null, easeIn: null, easeOut: null, shuffle: null, animIn: null, animOut: null, cssBefore: null, cssAfter: null, fxFn: null, height: "auto", startingSlide: 0, sync: 1, random: 0, fit: 0, containerResize: 1, pause: 0, pauseOnPagerHover: 0, autostop: 0, autostopCount: 0, delay: 0, slideExpr: null, cleartype: !$.support.opacity, nowrap: 0, fastOnEvent: 0, randomizeEffects: 1, rev: 0 }; })(jQuery);
/*
* jQuery Cycle Plugin Transition Definitions
* This script is a plugin for the jQuery Cycle Plugin
* Examples and documentation at: http://malsup.com/jquery/cycle/
* Copyright (c) 2007-2008 M. Alsup
* Version:	 2.52
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
(function($) { $.fn.cycle.transitions.scrollUp = function($cont, $slides, opts) { $cont.css("overflow", "hidden"); opts.before.push($.fn.cycle.commonReset); var h = $cont.height(); opts.cssBefore = { top: h, left: 0 }; opts.cssFirst = { top: 0 }; opts.animIn = { top: 0 }; opts.animOut = { top: -h }; }; $.fn.cycle.transitions.scrollDown = function($cont, $slides, opts) { $cont.css("overflow", "hidden"); opts.before.push($.fn.cycle.commonReset); var h = $cont.height(); opts.cssFirst = { top: 0 }; opts.cssBefore = { top: -h, left: 0 }; opts.animIn = { top: 0 }; opts.animOut = { top: h }; }; $.fn.cycle.transitions.scrollLeft = function($cont, $slides, opts) { $cont.css("overflow", "hidden"); opts.before.push($.fn.cycle.commonReset); var w = $cont.width(); opts.cssFirst = { left: 0 }; opts.cssBefore = { left: w, top: 0 }; opts.animIn = { left: 0 }; opts.animOut = { left: 0 - w }; }; $.fn.cycle.transitions.scrollRight = function($cont, $slides, opts) { $cont.css("overflow", "hidden"); opts.before.push($.fn.cycle.commonReset); var w = $cont.width(); opts.cssFirst = { left: 0 }; opts.cssBefore = { left: -w, top: 0 }; opts.animIn = { left: 0 }; opts.animOut = { left: w }; }; $.fn.cycle.transitions.scrollHorz = function($cont, $slides, opts) { $cont.css("overflow", "hidden").width(); opts.before.push(function(curr, next, opts, fwd) { $.fn.cycle.commonReset(curr, next, opts); opts.cssBefore.left = fwd ? (next.cycleW - 1) : (1 - next.cycleW); opts.animOut.left = fwd ? -curr.cycleW : curr.cycleW; }); opts.cssFirst = { left: 0 }; opts.cssBefore = { top: 0 }; opts.animIn = { left: 0 }; opts.animOut = { top: 0 }; }; $.fn.cycle.transitions.scrollVert = function($cont, $slides, opts) { $cont.css("overflow", "hidden"); opts.before.push(function(curr, next, opts, fwd) { $.fn.cycle.commonReset(curr, next, opts); opts.cssBefore.top = fwd ? (1 - next.cycleH) : (next.cycleH - 1); opts.animOut.top = fwd ? curr.cycleH : -curr.cycleH; }); opts.cssFirst = { top: 0 }; opts.cssBefore = { left: 0 }; opts.animIn = { top: 0 }; opts.animOut = { left: 0 }; }; $.fn.cycle.transitions.slideX = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $(opts.elements).not(curr).hide(); $.fn.cycle.commonReset(curr, next, opts, false, true); opts.animIn.width = next.cycleW; }); opts.cssBefore = { left: 0, top: 0, width: 0 }; opts.animIn = { width: "show" }; opts.animOut = { width: 0 }; }; $.fn.cycle.transitions.slideY = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $(opts.elements).not(curr).hide(); $.fn.cycle.commonReset(curr, next, opts, true, false); opts.animIn.height = next.cycleH; }); opts.cssBefore = { left: 0, top: 0, height: 0 }; opts.animIn = { height: "show" }; opts.animOut = { height: 0 }; }; $.fn.cycle.transitions.shuffle = function($cont, $slides, opts) { var w = $cont.css("overflow", "visible").width(); $slides.css({ left: 0, top: 0 }); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, true, true); }); opts.speed = opts.speed / 2; opts.random = 0; opts.shuffle = opts.shuffle || { left: -w, top: 15 }; opts.els = []; for (var i = 0; i < $slides.length; i++) { opts.els.push($slides[i]); } for (var i = 0; i < opts.currSlide; i++) { opts.els.push(opts.els.shift()); } opts.fxFn = function(curr, next, opts, cb, fwd) { var $el = fwd ? $(curr) : $(next); $(next).css(opts.cssBefore); var count = opts.slideCount; $el.animate(opts.shuffle, opts.speedIn, opts.easeIn, function() { var hops = $.fn.cycle.hopsFromLast(opts, fwd); for (var k = 0; k < hops; k++) { fwd ? opts.els.push(opts.els.shift()) : opts.els.unshift(opts.els.pop()); } if (fwd) { for (var i = 0, len = opts.els.length; i < len; i++) { $(opts.els[i]).css("z-index", len - i + count); } } else { var z = $(curr).css("z-index"); $el.css("z-index", parseInt(z) + 1 + count); } $el.animate({ left: 0, top: 0 }, opts.speedOut, opts.easeOut, function() { $(fwd ? this : curr).hide(); if (cb) { cb(); } }); }); }; opts.cssBefore = { display: "block", opacity: 1, top: 0, left: 0 }; }; $.fn.cycle.transitions.turnUp = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, false); opts.cssBefore.top = next.cycleH; opts.animIn.height = next.cycleH; }); opts.cssFirst = { top: 0 }; opts.cssBefore = { left: 0, height: 0 }; opts.animIn = { top: 0 }; opts.animOut = { height: 0 }; }; $.fn.cycle.transitions.turnDown = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, false); opts.animIn.height = next.cycleH; opts.animOut.top = curr.cycleH; }); opts.cssFirst = { top: 0 }; opts.cssBefore = { left: 0, top: 0, height: 0 }; opts.animOut = { height: 0 }; }; $.fn.cycle.transitions.turnLeft = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, false, true); opts.cssBefore.left = next.cycleW; opts.animIn.width = next.cycleW; }); opts.cssBefore = { top: 0, width: 0 }; opts.animIn = { left: 0 }; opts.animOut = { width: 0 }; }; $.fn.cycle.transitions.turnRight = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, false, true); opts.animIn.width = next.cycleW; opts.animOut.left = curr.cycleW; }); opts.cssBefore = { top: 0, left: 0, width: 0 }; opts.animIn = { left: 0 }; opts.animOut = { width: 0 }; }; $.fn.cycle.transitions.zoom = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, false, false, true); opts.cssBefore.top = next.cycleH / 2; opts.cssBefore.left = next.cycleW / 2; opts.animIn = { top: 0, left: 0, width: next.cycleW, height: next.cycleH }; opts.animOut = { width: 0, height: 0, top: curr.cycleH / 2, left: curr.cycleW / 2 }; }); opts.cssFirst = { top: 0, left: 0 }; opts.cssBefore = { width: 0, height: 0 }; }; $.fn.cycle.transitions.fadeZoom = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, false, false); opts.cssBefore.left = next.cycleW / 2; opts.cssBefore.top = next.cycleH / 2; opts.animIn = { top: 0, left: 0, width: next.cycleW, height: next.cycleH }; }); opts.cssBefore = { width: 0, height: 0 }; opts.animOut = { opacity: 0 }; }; $.fn.cycle.transitions.blindX = function($cont, $slides, opts) { var w = $cont.css("overflow", "hidden").width(); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts); opts.animIn.width = next.cycleW; opts.animOut.left = curr.cycleW; }); opts.cssBefore = { left: w, top: 0 }; opts.animIn = { left: 0 }; opts.animOut = { left: w }; }; $.fn.cycle.transitions.blindY = function($cont, $slides, opts) { var h = $cont.css("overflow", "hidden").height(); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts); opts.animIn.height = next.cycleH; opts.animOut.top = curr.cycleH; }); opts.cssBefore = { top: h, left: 0 }; opts.animIn = { top: 0 }; opts.animOut = { top: h }; }; $.fn.cycle.transitions.blindZ = function($cont, $slides, opts) { var h = $cont.css("overflow", "hidden").height(); var w = $cont.width(); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts); opts.animIn.height = next.cycleH; opts.animOut.top = curr.cycleH; }); opts.cssBefore = { top: h, left: w }; opts.animIn = { top: 0, left: 0 }; opts.animOut = { top: h, left: w }; }; $.fn.cycle.transitions.growX = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, false, true); opts.cssBefore.left = this.cycleW / 2; opts.animIn = { left: 0, width: this.cycleW }; opts.animOut = { left: 0 }; }); opts.cssBefore = { width: 0, top: 0 }; }; $.fn.cycle.transitions.growY = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, false); opts.cssBefore.top = this.cycleH / 2; opts.animIn = { top: 0, height: this.cycleH }; opts.animOut = { top: 0 }; }); opts.cssBefore = { height: 0, left: 0 }; }; $.fn.cycle.transitions.curtainX = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, false, true, true); opts.cssBefore.left = next.cycleW / 2; opts.animIn = { left: 0, width: this.cycleW }; opts.animOut = { left: curr.cycleW / 2, width: 0 }; }); opts.cssBefore = { top: 0, width: 0 }; }; $.fn.cycle.transitions.curtainY = function($cont, $slides, opts) { opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, false, true); opts.cssBefore.top = next.cycleH / 2; opts.animIn = { top: 0, height: next.cycleH }; opts.animOut = { top: curr.cycleH / 2, height: 0 }; }); opts.cssBefore = { left: 0, height: 0 }; }; $.fn.cycle.transitions.cover = function($cont, $slides, opts) { var d = opts.direction || "left"; var w = $cont.css("overflow", "hidden").width(); var h = $cont.height(); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts); if (d == "right") { opts.cssBefore.left = -w; } else { if (d == "up") { opts.cssBefore.top = h; } else { if (d == "down") { opts.cssBefore.top = -h; } else { opts.cssBefore.left = w; } } } }); opts.animIn = { left: 0, top: 0 }; opts.animOut = { opacity: 1 }; opts.cssBefore = { top: 0, left: 0 }; }; $.fn.cycle.transitions.uncover = function($cont, $slides, opts) { var d = opts.direction || "left"; var w = $cont.css("overflow", "hidden").width(); var h = $cont.height(); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, true, true); if (d == "right") { opts.animOut.left = w; } else { if (d == "up") { opts.animOut.top = -h; } else { if (d == "down") { opts.animOut.top = h; } else { opts.animOut.left = -w; } } } }); opts.animIn = { left: 0, top: 0 }; opts.animOut = { opacity: 1 }; opts.cssBefore = { top: 0, left: 0 }; }; $.fn.cycle.transitions.toss = function($cont, $slides, opts) { var w = $cont.css("overflow", "visible").width(); var h = $cont.height(); opts.before.push(function(curr, next, opts) { $.fn.cycle.commonReset(curr, next, opts, true, true, true); if (!opts.animOut.left && !opts.animOut.top) { opts.animOut = { left: w * 2, top: -h / 2, opacity: 0 }; } else { opts.animOut.opacity = 0; } }); opts.cssBefore = { left: 0, top: 0 }; opts.animIn = { left: 0 }; }; $.fn.cycle.transitions.wipe = function($cont, $slides, opts) { var w = $cont.css("overflow", "hidden").width(); var h = $cont.height(); opts.cssBefore = opts.cssBefore || {}; var clip; if (opts.clip) { if (/l2r/.test(opts.clip)) { clip = "rect(0px 0px " + h + "px 0px)"; } else { if (/r2l/.test(opts.clip)) { clip = "rect(0px " + w + "px " + h + "px " + w + "px)"; } else { if (/t2b/.test(opts.clip)) { clip = "rect(0px " + w + "px 0px 0px)"; } else { if (/b2t/.test(opts.clip)) { clip = "rect(" + h + "px " + w + "px " + h + "px 0px)"; } else { if (/zoom/.test(opts.clip)) { var t = parseInt(h / 2); var l = parseInt(w / 2); clip = "rect(" + t + "px " + l + "px " + t + "px " + l + "px)"; } } } } } } opts.cssBefore.clip = opts.cssBefore.clip || clip || "rect(0px 0px 0px 0px)"; var d = opts.cssBefore.clip.match(/(\d+)/g); var t = parseInt(d[0]), r = parseInt(d[1]), b = parseInt(d[2]), l = parseInt(d[3]); opts.before.push(function(curr, next, opts) { if (curr == next) { return; } var $curr = $(curr), $next = $(next); $.fn.cycle.commonReset(curr, next, opts, true, true, false); opts.cssAfter.display = "block"; var step = 1, count = parseInt((opts.speedIn / 13)) - 1; (function f() { var tt = t ? t - parseInt(step * (t / count)) : 0; var ll = l ? l - parseInt(step * (l / count)) : 0; var bb = b < h ? b + parseInt(step * ((h - b) / count || 1)) : h; var rr = r < w ? r + parseInt(step * ((w - r) / count || 1)) : w; $next.css({ clip: "rect(" + tt + "px " + rr + "px " + bb + "px " + ll + "px)" }); (step++ <= count) ? setTimeout(f, 13) : $curr.css("display", "none"); })(); }); opts.cssBefore = { display: "block", opacity: 1, top: 0, left: 0 }; opts.animIn = { left: 0 }; opts.animOut = { left: 0 }; }; })(jQuery);


function Search() {
    var duedate = document.getElementById("input_date").value;
    var parseddate = Date.parse(duedate);
    var ZIP = new String();
    ZIP = document.getElementById("input_ZIP").value;

    var qs = window.location.search.substring(1);
    var qs2 = qs.split("&");
    var qstype = qs2[0].split("=");
    var DoulaType = qstype[1];
    if (DoulaType == null) {
        DoulaType = "Birth";
    }
    
    if (parseddate == null || ZIP.length < 5 || ZIP.length > 6) {
        $("#datesearcherror").text("Please enter a date and 5 digit US ZIP code or 6 character Canadian postal code (no space)");
        $("#datesearcherror").addClass('errormsg');
    }
    
    else {
        $.ajax({
            type: "POST",
            data: '{ZIP: "' + ZIP + '" }',
            url: "DoulaCalendar.asmx/ZIPChecker",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(response) {
                var retcode = response.d;
                if (retcode == 1) {
                    var urlredir = new String();
                    urlredir = "/list/" + DoulaType.toLowerCase() + "/" + ZIP + "/" + parseddate.toString('yyyy/MM/dd');
                    $.ajax({
                        type: "POST",
                        data: '{type: "' + DoulaType + '", ZIP: "' + ZIP + '", DueDate: "' + parseddate.toString('MM/dd/yyyy') + '" }',
                        url: "/DoulaCalendar.asmx/SearchLogger",
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function(response) {
                            window.location.href = urlredir;
                        },
                        failure: function(msg) {
                            window.location.href = urlredir;
                        }
                    });

                }
                else {
                    $("#datesearcherror").text("That postal code was not found");
                    $("#datesearcherror").addClass('errormsg');
                }
            },
            failure: function(msg) {

            $("#datesearcherror").text("That postal code was not found");
            $("#datesearcherror").addClass('errormsg');
            }
        });
    }
}

function ShowDoulas() {
    var qs = window.location.search.substring(1);
    var qs2 = qs.split("&");
    var qstype = qs2[0].split("=");
    var DoulaType = qstype[1];

    var ZIP = document.getElementById("input_ZIP_listall").value;
    if (ZIP.length < 5 || ZIP.length > 6) {
        $("#ZIPsearcherror").text("Please enter a 5 digit US ZIP code or a 6 character Canadian postal code (no space), or just click on a state/province to see all doulas in that region");
        $("#ZIPsearcherror").addClass('errormsg');
    }
    
    
     else {
        $.ajax({
            type: "POST",
            data: '{ZIP: "' + ZIP + '" }',
            url: "/DoulaCalendar.asmx/ZIPChecker",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(response) {
                var retcode = response.d;
                if (retcode == 1) {
                    var urlredir = new String();
                    urlredir = "/list/" + DoulaType.toLowerCase() + "/" + ZIP;
                    $.ajax({
                        type: "POST",
                        data: '{type: "' + DoulaType + '", ZIP: "' + ZIP + '", DueDate: "NA" }',
                        url: "/DoulaCalendar.asmx/SearchLogger",
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function(response) {
                            window.location.href = urlredir;
                        },
                        failure: function(msg) {
                            window.location.href = urlredir;
                        }
                    });

                }
                else {
                    $("#datesearcherror").text("That postal code was not found");
                    $("#datesearcherror").addClass('errormsg');
                }
            },
            failure: function(msg) {

            $("#datesearcherror").text("That postal code was not found");
            $("#datesearcherror").addClass('errormsg');
            }
        });
    }
}


function DoulasByName() {
    var qs = window.location.search.substring(1);
    var qs2 = qs.split("&");
    var qstype = qs2[0].split("=");
    var DoulaType = qstype[1];

    var Name = document.getElementById("input_name").value;
    Name = jQuery.trim(Name);
    var Names = Name.split(" ");
    if (Name.length == 0) {
        $("#namesearcherror").text("Please enter a name to search");
        $("#namesearcherror").addClass('errormsg');
    }
    else if (Name.length > 50) {
    $("#namesearcherror").text("Please enter a shorter name (max length 50)");
    $("#namesearcherror").addClass('errormsg');
    }
    else if (Names.length > 3) {
    $("#namesearcherror").text("Please enter no more than 3 words in the name search box");
    $("#namesearcherror").addClass('errormsg');
    }
    else {
        $.ajax({
            type: "POST",
            data: '{type:"' + DoulaType + '", Name: "' + Name + '", DueDate: "NA" }',
            url: "/DoulaCalendar.asmx/NameSearchLogger",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(response) {
                window.location.href = "/list/" + DoulaType.toLowerCase() + "/" + escape(Name);
            },
            failure: function(msg) {
                window.location.href = "/list/" + DoulaType.toLowerCase() + "/" + escape(Name);
            }
        });


    }
}

$(document).ready(function() {
    var qs = window.location.search.substring(1);
    if (qs != "") {
        var qs2 = qs.split("&");
        var qstype = qs2[0].split("=");
        var DoulaType = qstype[1];
        duedatecal = new YAHOO.widget.Calendar("duedatecal", "cal_duedate", { close: true });
        duedatecal.render();
        $('#cal_duedate').hide();
        $('#cal_img').click(function() {
            $('#cal_duedate').toggle(400);
            return false;
        });
        duedatecal.selectEvent.subscribe(handleCalSelect, duedatecal, true);

    }
    else {
        $('.homethumbs').css({ 'visibility': 'visible' });
        $('.homethumbs').cycle({
            fx: 'fade', pause: 1, speed: 3000
        });
    }

    function handleCalSelect(type, args, obj) {
        var dates = args[0];
        var date = dates[0];
        var year = date[0], month = date[1], day = date[2];
        $('#cal_duedate').hide('fast');
        var txtfrom = document.getElementById("input_date");
        txtfrom.value = month + "/" + day + "/" + year;
        Search();
    }
})