# Yahoo! UI Library

### Calendar 2.8.0r4

Yahoo! UI Library > calendar > CalendarNavigator.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * 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_CONFIG
         * @type Object
         * @static
         */
        CN.DEFAULT_CONFIG = {
            strings : {
                month: "Month",
                year: "Year",
                submit: "Okay",
                cancel: "Cancel",
                invalidYear : "Year needs to be a number"
            },
            monthFormat: YAHOO.widget.Calendar.LONG,
            initialFocus: "year"
        };

        /**
         * Object literal containing the default configuration values for the CalendarNavigator
         * @property _DEFAULT_CFG
         * @protected
         * @deprecated Made public. See the public DEFAULT_CONFIG property
         * @type Object
         * @static
         */
        CN._DEFAULT_CFG = CN.DEFAULT_CONFIG;

        /**
         * 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 IE Quirks
             * @private
             * @property __isIEQuirks
             */
            var ie = YAHOO.env.ua.ie;
            this.__isIEQuirks = (ie && ((ie <= 6) || (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 or IE in 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) {
            var date = YAHOO.widget.DateMath.getDate(this.getYear() - cal.cfg.getProperty("YEAR_OFFSET"), this.getMonth(), 1);
            cal.cfg.setProperty("pagedate", date);
            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_CONFIG
         * </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_CONFIG;
            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)

    };

Copyright © 2009 Yahoo! Inc. All rights reserved.