#

API Docs for: 3.10.3
Show: Inherited Protected Private Deprecated

# File: calendar/js/calendar-base.js

    /**
     * The CalendarBase submodule is a basic UI calendar view that displays
     * a range of dates in a two-dimensional month grid, with one or more
     * months visible at a single time. CalendarBase supports custom date
     * rendering, multiple calendar panes, and selection.
     * @module calendar
     * @submodule calendar-base
     */

    var getCN                 = Y.ClassNameManager.getClassName,
        CALENDAR              = 'calendar',
        CAL_GRID              = getCN(CALENDAR, 'grid'),
        CAL_LEFT_GRID         = getCN(CALENDAR, 'left-grid'),
        CAL_RIGHT_GRID        = getCN(CALENDAR, 'right-grid'),
        CAL_BODY              = getCN(CALENDAR, 'body'),
        CAL_HD                = getCN(CALENDAR, 'header'),
        CAL_HD_LABEL          = getCN(CALENDAR, 'header-label'),
        CAL_WDAYROW           = getCN(CALENDAR, 'weekdayrow'),
        CAL_WDAY              = getCN(CALENDAR, 'weekday'),
        CAL_COL_HIDDEN        = getCN(CALENDAR, 'column-hidden'),
        CAL_DAY_SELECTED      = getCN(CALENDAR, 'day-selected'),
        SELECTION_DISABLED    = getCN(CALENDAR, 'selection-disabled'),
        CAL_ROW               = getCN(CALENDAR, 'row'),
        CAL_DAY               = getCN(CALENDAR, 'day'),
        CAL_PREVMONTH_DAY     = getCN(CALENDAR, 'prevmonth-day'),
        CAL_NEXTMONTH_DAY     = getCN(CALENDAR, 'nextmonth-day'),
        CAL_ANCHOR            = getCN(CALENDAR, 'anchor'),
        CAL_PANE              = getCN(CALENDAR, 'pane'),
        CAL_STATUS            = getCN(CALENDAR, 'status'),
        L           = Y.Lang,
        substitute  = L.sub,
        arrayEach   = Y.Array.each,
        objEach     = Y.Object.each,
        iOf         = Y.Array.indexOf,
        hasKey      = Y.Object.hasKey,
        setVal      = Y.Object.setValue,
        isEmpty     = Y.Object.isEmpty,
        ydate       = Y.DataType.Date;

    /** Create a calendar view to represent a single or multiple
        * month range of dates, rendered as a grid with date and
        * weekday labels.
        *
        * @class CalendarBase
        * @extends Widget
        * @param config {Object} Configuration object (see Configuration
        * attributes)
        * @constructor
        */
    function CalendarBase() {
        CalendarBase.superclass.constructor.apply ( this, arguments );
    }

    Y.CalendarBase = Y.extend( CalendarBase, Y.Widget, {

        /**
         * A storage for various properties of individual month
         * panes.
         *
         * @property _paneProperties
         * @type Object
         * @private
         */
        _paneProperties : {},

        /**
         * The number of month panes in the calendar, deduced
         * from the CONTENT_TEMPLATE's number of {calendar_grid}
         * tokens.
         *
         * @property _paneNumber
         * @type Number
         * @private
         */
        _paneNumber : 1,

        /**
         * The unique id used to prefix various elements of this
         * calendar instance.
         *
         * @property _calendarId
         * @type String
         * @private
         */
        _calendarId : null,

        /**
         * The hash map of selected dates, populated with
         * selectDates() and deselectDates() methods
         *
         * @property _selectedDates
         * @type Object
         * @private
         */
        _selectedDates : {},

        /**
         * A private copy of the rules object, populated
         * by setting the customRenderer attribute.
         *
         * @property _rules
         * @type Object
         * @private
         */
        _rules : {},

        /**
         * A private copy of the filterFunction, populated
         * by setting the customRenderer attribute.
         *
         * @property _filterFunction
         * @type Function
         * @private
         */
        _filterFunction : null,

        /**
         * Storage for calendar cells modified by any custom
         * formatting. The storage is cleared, used to restore
         * cells to the original state, and repopulated accordingly
         * when the calendar is rerendered.
         *
         * @property _storedDateCells
         * @type Object
         * @private
         */
        _storedDateCells : {},

        /**
         * Designated initializer
         * Initializes instance-level properties of
         * calendar.
         *
         * @method initializer
         */
        initializer : function () {
            this._paneProperties = {};
            this._calendarId = Y.guid('calendar');
            this._selectedDates = {};
            if (isEmpty(this._rules)) {
                 this._rules = {};
            }
            this._storedDateCells = {};
        },

        /**
         * renderUI implementation
         *
         * Creates a visual representation of the calendar based on existing parameters.
         * @method renderUI
         */
        renderUI : function () {

            var contentBox = this.get('contentBox');
            contentBox.appendChild(this._initCalendarHTML(this.get('date')));

            if (this.get('showPrevMonth')) {
                    this._afterShowPrevMonthChange();
            }
            if (this.get('showNextMonth')) {
                    this._afterShowNextMonthChange();
            }

            this._renderCustomRules();
            this._renderSelectedDates();

            this.get("boundingBox").setAttribute("aria-labelledby", this._calendarId + "_header");

        },

        /**
         * bindUI implementation
         *
         * Assigns listeners to relevant events that change the state
         * of the calendar.
         * @method bindUI
         */
        bindUI : function () {
            this.after('dateChange', this._afterDateChange);
            this.after('showPrevMonthChange', this._afterShowPrevMonthChange);
            this.after('showNextMonthChange', this._afterShowNextMonthChange);
            this.after('headerRendererChange', this._afterHeaderRendererChange);
            this.after('customRendererChange', this._afterCustomRendererChange);
            this.after('enabledDatesRuleChange', this._afterCustomRendererChange);
            this.after('disabledDatesRuleChange', this._afterCustomRendererChange);
            this.after('focusedChange', this._afterFocusedChange);
            this.after('selectionChange', this._renderSelectedDates);
            this._bindCalendarEvents();
        },

        /**
         * An internal utility method that generates a list of selected dates
         * from the hash storage.
         *
         * @method _getSelectedDatesList
         * @protected
         * @return {Array} The array of `Date`s that are currently selected.
         */
        _getSelectedDatesList : function () {
            var output = [];

            objEach (this._selectedDates, function (year) {
                objEach (year, function (month) {
                    objEach (month, function (day) {
                        output.push (day);
                    }, this);
                }, this);
            }, this);

            return output;
        },

        /**
         * A utility method that returns all dates selected in a specific month.
         *
         * @method _getSelectedDatesInMonth
         * @param {Date} oDate corresponding to the month for which selected dates
         * are requested.
         * @protected
         * @return {Array} The array of `Date`s in a given month that are currently selected.
         */
        _getSelectedDatesInMonth : function (oDate) {
            var year = oDate.getFullYear(),
                month = oDate.getMonth();

            if (hasKey(this._selectedDates, year) && hasKey(this._selectedDates[year], month)) {
                return Y.Object.values(this._selectedDates[year][month]);
            } else {
                return [];
            }
        },

        /**
         * An internal parsing method that receives a String list of numbers
         * and number ranges (of the form "1,2,3,4-6,7-9,10,11" etc.) and checks
         * whether a specific number is included in this list. Used for looking
         * up dates in the customRenderer rule set.
         *
         * @method _isNumInList
         * @param {Number} num The number to look for in a list.
         * @param {String} strList The list of numbers of the form "1,2,3,4-6,7-8,9", etc.
         * @private
         * @return {boolean} Returns true if the given number is in the given list.
         */
        _isNumInList : function (num, strList) {
            if (strList === "all") {
                return true;
            } else {
                var elements = strList.split(","),
                    i = elements.length,
                    range;

                while (i--) {
                    range = elements[i].split("-");
                    if (range.length === 2 && num >= parseInt(range[0], 10) && num <= parseInt(range[1], 10)) {
                        return true;
                    }
                    else if (range.length === 1 && (parseInt(elements[i], 10) === num)) {
                        return true;
                    }
                }
                return false;
            }
        },

        /**
         * Given a specific date, returns an array of rules (from the customRenderer rule set)
         * that the given date matches.
         *
         * @method _getRulesForDate
         * @param {Date} oDate The date for which an array of rules is needed
         * @private
         * @return {Array} Returns an array of `String`s, each containg the name of
         * a rule that the given date matches.
         */
        _getRulesForDate : function (oDate) {
            var year = oDate.getFullYear(),
                    month = oDate.getMonth(),
                    date = oDate.getDate(),
                    wday = oDate.getDay(),
                    rules = this._rules,
                    outputRules = [],
                    years, months, dates, days;

            for (years in rules) {
                if (this._isNumInList(year, years)) {
                    if (L.isString(rules[years])) {
                            outputRules.push(rules[years]);
                    }
                    else {
                        for (months in rules[years]) {
                            if (this._isNumInList(month, months)) {
                                if (L.isString(rules[years][months])) {
                                        outputRules.push(rules[years][months]);
                                }
                                else {
                                    for (dates in rules[years][months]) {
                                        if (this._isNumInList(date, dates)) {
                                            if (L.isString(rules[years][months][dates])) {
                                                    outputRules.push(rules[years][months][dates]);
                                            }
                                            else {
                                                for (days in rules[years][months][dates]) {
                                                    if (this._isNumInList(wday, days)) {
                                                        if (L.isString(rules[years][months][dates][days])) {
                                                            outputRules.push(rules[years][months][dates][days]);
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return outputRules;
        },

        /**
         * A utility method which, given a specific date and a name of the rule,
         * checks whether the date matches the given rule.
         *
         * @method _matchesRule
         * @param {Date} oDate The date to check
         * @param {String} rule The name of the rule that the date should match.
         * @private
         * @return {boolean} Returns true if the date matches the given rule.
         *
         */
        _matchesRule : function (oDate, rule) {
            return (iOf(this._getRulesForDate(oDate), rule) >= 0);
        },

        /**
         * A utility method which checks whether a given date matches the `enabledDatesRule`
         * or does not match the `disabledDatesRule` and therefore whether it can be selected.
         * @method _canBeSelected
         * @param {Date} oDate The date to check
         * @private
         * @return {boolean} Returns true if the date can be selected; false otherwise.
         */
        _canBeSelected : function (oDate) {

            var enabledDatesRule = this.get("enabledDatesRule"),
                disabledDatesRule = this.get("disabledDatesRule");

            if (enabledDatesRule) {
                return this._matchesRule(oDate, enabledDatesRule);
            } else if (disabledDatesRule) {
                return !this._matchesRule(oDate, disabledDatesRule);
            } else {
                return true;
            }
        },

        /**
         * Selects a given date or array of dates.
         * @method selectDates
         * @param {Date|Array} dates A `Date` or `Array` of `Date`s.
         * @return {CalendarBase} A reference to this object
         * @chainable
         */
        selectDates : function (dates) {
            if (ydate.isValidDate(dates)) {
                this._addDateToSelection(dates);
            }
            else if (L.isArray(dates)) {
                this._addDatesToSelection(dates);
            }
            return this;
        },

        /**
         * Deselects a given date or array of dates, or deselects
         * all dates if no argument is specified.
         * @method deselectDates
         * @param {Date|Array} [dates] A `Date` or `Array` of `Date`s, or no
         * argument if all dates should be deselected.
         * @return {CalendarBase} A reference to this object
         * @chainable
         */
        deselectDates : function (dates) {
            if (!dates) {
                this._clearSelection();
            }
            else if (ydate.isValidDate(dates)) {
                this._removeDateFromSelection(dates);
            }
            else if (L.isArray(dates)) {
                this._removeDatesFromSelection(dates);
            }
            return this;
        },

        /**
         * A utility method that adds a given date to selection..
         * @method _addDateToSelection
         * @param {Date} oDate The date to add to selection.
         * @param {Number} [index] An optional parameter that is used
         * to differentiate between individual date selections and multiple
         * date selections.
         * @private
         */
        _addDateToSelection : function (oDate, index) {

            if (this._canBeSelected(oDate)) {

                var year = oDate.getFullYear(),
                    month = oDate.getMonth(),
                    day = oDate.getDate();

                if (hasKey(this._selectedDates, year)) {
                    if (hasKey(this._selectedDates[year], month)) {
                        this._selectedDates[year][month][day] = oDate;
                    } else {
                        this._selectedDates[year][month] = {};
                        this._selectedDates[year][month][day] = oDate;
                    }
                } else {
                    this._selectedDates[year] = {};
                    this._selectedDates[year][month] = {};
                    this._selectedDates[year][month][day] = oDate;
                }

                this._selectedDates = setVal(this._selectedDates, [year, month, day], oDate);

                if (!index) {
                    this._fireSelectionChange();
                }
            }
        },

        /**
         * A utility method that adds a given list of dates to selection.
         * @method _addDatesToSelection
         * @param {Array} datesArray The list of dates to add to selection.
         * @private
         */
        _addDatesToSelection : function (datesArray) {
            arrayEach(datesArray, this._addDateToSelection, this);
            this._fireSelectionChange();
        },

        /**
         * A utility method that adds a given range of dates to selection.
         * @method _addDateRangeToSelection
         * @param {Date} startDate The first date of the given range.
         * @param {Date} endDate The last date of the given range.
         * @private
         */
        _addDateRangeToSelection : function (startDate, endDate) {

            var timezoneDifference = (endDate.getTimezoneOffset() - startDate.getTimezoneOffset())*60000,
                startTime = startDate.getTime(),
                endTime   = endDate.getTime(),
                tempTime,
                time,
                addedDate;

            if (startTime > endTime) {
                tempTime = startTime;
                startTime = endTime;
                endTime = tempTime + timezoneDifference;
            } else {
                endTime = endTime - timezoneDifference;
            }

            for (time = startTime; time <= endTime; time += 86400000) {
                addedDate = new Date(time);
                addedDate.setHours(12);
                this._addDateToSelection(addedDate, time);
            }
            this._fireSelectionChange();
        },

        /**
         * A utility method that removes a given date from selection..
         * @method _removeDateFromSelection
         * @param {Date} oDate The date to remove from selection.
         * @param {Number} [index] An optional parameter that is used
         * to differentiate between individual date selections and multiple
         * date selections.
         * @private
         */
        _removeDateFromSelection : function (oDate, index) {
            var year = oDate.getFullYear(),
                month = oDate.getMonth(),
                day = oDate.getDate();

            if (hasKey(this._selectedDates, year) &&
                hasKey(this._selectedDates[year], month) &&
                hasKey(this._selectedDates[year][month], day)
            ) {
                delete this._selectedDates[year][month][day];
                if (!index) {
                    this._fireSelectionChange();
                }
            }
        },

        /**
         * A utility method that removes a given list of dates from selection.
         * @method _removeDatesFromSelection
         * @param {Array} datesArray The list of dates to remove from selection.
         * @private
         */
        _removeDatesFromSelection : function (datesArray) {
            arrayEach(datesArray, this._removeDateFromSelection, this);
            this._fireSelectionChange();
        },

        /**
         * A utility method that removes a given range of dates from selection.
         * @method _removeDateRangeFromSelection
         * @param {Date} startDate The first date of the given range.
         * @param {Date} endDate The last date of the given range.
         * @private
         */
        _removeDateRangeFromSelection : function (startDate, endDate) {
            var startTime = startDate.getTime(),
                endTime   = endDate.getTime(),
                time;

            for (time = startTime; time <= endTime; time += 86400000) {
                this._removeDateFromSelection(new Date(time), time);
            }

            this._fireSelectionChange();
        },

        /**
         * A utility method that removes all dates from selection.
         * @method _clearSelection
         * @param {boolean} noevent A Boolean specifying whether a selectionChange
         * event should be fired. If true, the event is not fired.
         * @private
         */
        _clearSelection : function (noevent) {
            this._selectedDates = {};
            this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
            if (!noevent) {
                this._fireSelectionChange();
            }
        },

        /**
         * A utility method that fires a selectionChange event.
         * @method _fireSelectionChange
         * @private
         */
        _fireSelectionChange : function () {

            /**
            * Fired when the set of selected dates changes. Contains a payload with
            * a `newSelection` property with an array of selected dates.
            *
            * @event selectionChange
            */
            this.fire("selectionChange", {newSelection: this._getSelectedDatesList()});
        },

        /**
         * A utility method that restores cells modified by custom formatting.
         * @method _restoreModifiedCells
         * @private
         */
        _restoreModifiedCells : function () {
            var contentbox = this.get("contentBox"),
                id;
            for (id in this._storedDateCells) {
                contentbox.one("#" + id).replace(this._storedDateCells[id]);
                delete this._storedDateCells[id];
            }
        },

        /**
         * A rendering assist method that renders all cells modified by the customRenderer
         * rules, as well as the enabledDatesRule and disabledDatesRule.
         * @method _renderCustomRules
         * @private
         */
        _renderCustomRules : function () {

            this.get("contentBox").all("." + CAL_DAY + ",." + CAL_NEXTMONTH_DAY).removeClass(SELECTION_DISABLED).setAttribute("aria-disabled", false);

            if (!isEmpty(this._rules)) {
                var paneNum,
                    paneDate,
                    dateArray;

                for (paneNum = 0; paneNum < this._paneNumber; paneNum++) {
                    paneDate = ydate.addMonths(this.get("date"), paneNum);
                    dateArray = ydate.listOfDatesInMonth(paneDate);
                    arrayEach(dateArray, Y.bind(this._renderCustomRulesHelper, this));
                }
            }
        },

        /**
        * A handler for a date selection event (either a click or a keyboard
        *   selection) that adds the appropriate CSS class to a specific DOM
        *   node corresponding to the date and sets its aria-selected
        *   attribute to true.
        *
        * @method _renderCustomRulesHelper
        * @private
        */
        _renderCustomRulesHelper: function (date) {
            var enRule = this.get("enabledDatesRule"),
                disRule = this.get("disabledDatesRule"),
                matchingRules,
                dateNode;

            matchingRules = this._getRulesForDate(date);
            if (matchingRules.length > 0) {
                dateNode = this._dateToNode(date);
                if ((enRule && iOf(matchingRules, enRule) < 0) || (!enRule && disRule && iOf(matchingRules, disRule) >= 0)) {
                    dateNode.addClass(SELECTION_DISABLED).setAttribute("aria-disabled", true);
                }

                if (L.isFunction(this._filterFunction)) {
                    this._storedDateCells[dateNode.get("id")] = dateNode.cloneNode(true);
                    this._filterFunction (date, dateNode, matchingRules);
                }
            } else if (enRule) {
                 dateNode = this._dateToNode(date);
                 dateNode.addClass(SELECTION_DISABLED).setAttribute("aria-disabled", true);
            }
        },

        /**
         * A rendering assist method that renders all cells that are currently selected.
         * @method _renderSelectedDates
         * @private
         */
        _renderSelectedDates : function () {
            this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);

            var paneNum,
                paneDate,
                dateArray;

            for (paneNum = 0; paneNum < this._paneNumber; paneNum++) {
                paneDate = ydate.addMonths(this.get("date"), paneNum);
                dateArray = this._getSelectedDatesInMonth(paneDate);

                arrayEach(dateArray, Y.bind(this._renderSelectedDatesHelper, this));
            }
        },

        /**
        * Takes in a date and determines whether that date has any rules
        *   matching it in the customRenderer; then calls the specified
        *   filterFunction if that's the case and/or disables the date
        *   if the rule is specified as a disabledDatesRule.
        *
        * @method _renderSelectedDatesHelper
        * @private
        */
        _renderSelectedDatesHelper: function (date) {
            this._dateToNode(date).addClass(CAL_DAY_SELECTED).setAttribute("aria-selected", true);
        },

        /**
         * A utility method that converts a date to the node wrapping the calendar cell
         * the date corresponds to..
         * @method _dateToNode
         * @param {Date} oDate The date to convert to Node
         * @protected
         * @return {Node} The node wrapping the DOM element of the cell the date
         * corresponds to.
         */
        _dateToNode : function (oDate) {
            var day = oDate.getDate(),
                col = 0,
                daymod = day%7,
                paneNum = (12 + oDate.getMonth() - this.get("date").getMonth()) % 12,
                paneId = this._calendarId + "_pane_" + paneNum,
                cutoffCol = this._paneProperties[paneId].cutoffCol;

            switch (daymod) {
                case (0):
                    if (cutoffCol >= 6) {
                        col = 12;
                    } else {
                        col = 5;
                    }
                    break;
                case (1):
                        col = 6;
                    break;
                case (2):
                    if (cutoffCol > 0) {
                        col = 7;
                    } else {
                        col = 0;
                    }
                    break;
                case (3):
                    if (cutoffCol > 1) {
                        col = 8;
                    } else {
                        col = 1;
                    }
                    break;
                case (4):
                    if (cutoffCol > 2) {
                        col = 9;
                    } else {
                        col = 2;
                    }
                    break;
                case (5):
                    if (cutoffCol > 3) {
                        col = 10;
                    } else {
                        col = 3;
                    }
                    break;
                case (6):
                    if (cutoffCol > 4) {
                        col = 11;
                    } else {
                        col = 4;
                    }
                    break;
            }
            return(this.get("contentBox").one("#" + this._calendarId + "_pane_" + paneNum + "_" + col + "_" + day));

        },

        /**
         * A utility method that converts a node corresponding to the DOM element of
         * the cell for a particular date to that date.
         * @method _nodeToDate
         * @param {Node} oNode The Node wrapping the DOM element of a particular date cell.
         * @protected
         * @return {Date} The date corresponding to the DOM element that the given node wraps.
         */
        _nodeToDate : function (oNode) {

            var idParts = oNode.get("id").split("_").reverse(),
                paneNum = parseInt(idParts[2], 10),
                day  = parseInt(idParts[0], 10),
                shiftedDate = ydate.addMonths(this.get("date"), paneNum),
                year = shiftedDate.getFullYear(),
                month = shiftedDate.getMonth();

            return new Date(year, month, day, 12, 0, 0, 0);
        },

        /**
         * A placeholder method, called from bindUI, to bind the Calendar events.
         * @method _bindCalendarEvents
         * @protected
         */
        _bindCalendarEvents : function () {},

        /**
         * A utility method that normalizes a given date by converting it to the 1st
         * day of the month the date is in, with the time set to noon.
         * @method _normalizeDate
         * @param {Date} oDate The date to normalize
         * @protected
         * @return {Date} The normalized date, set to the first of the month, with time
         * set to noon.
         */
        _normalizeDate : function (date) {
            if (date) {
                return new Date(date.getFullYear(), date.getMonth(), 1, 12, 0, 0, 0);
            } else {
                return null;
            }
        },

        /**
         * A render assist utility method that computes the cutoff column for the calendar
         * rendering mask.
         * @method _getCutoffColumn
         * @param {Date} date The date of the month grid to compute the cutoff column for.
         * @param {Number} firstday The first day of the week (modified by internationalized calendars)
         * @private
         * @return {Number} The number of the cutoff column.
         */
        _getCutoffColumn : function (date, firstday) {
            var distance = this._normalizeDate(date).getDay() - firstday,
                cutOffColumn = 6 - (distance + 7) % 7;
            return cutOffColumn;
        },

        /**
         * A render assist method that turns on the view of the previous month's dates
         * in a given calendar pane.
         * @method _turnPrevMonthOn
         * @param {Node} pane The calendar pane that needs its previous month's dates view
         * modified.
         * @protected
         */
        _turnPrevMonthOn : function (pane) {
            var pane_id = pane.get("id"),
                pane_date = this._paneProperties[pane_id].paneDate,
                daysInPrevMonth = ydate.daysInMonth(ydate.addMonths(pane_date, -1)),
                cell;

            if (!this._paneProperties[pane_id].hasOwnProperty("daysInPrevMonth")) {
                this._paneProperties[pane_id].daysInPrevMonth = 0;
            }

            if (daysInPrevMonth !== this._paneProperties[pane_id].daysInPrevMonth) {

                this._paneProperties[pane_id].daysInPrevMonth = daysInPrevMonth;

                for (cell = 5; cell >= 0; cell--) {
                    pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).set('text', daysInPrevMonth--);
                }
            }
        },

        /**
         * A render assist method that turns off the view of the previous month's dates
         * in a given calendar pane.
         * @method _turnPrevMonthOff
         * @param {Node} pane The calendar pane that needs its previous month's dates view
         * modified.
         * @protected
         */
        _turnPrevMonthOff : function (pane) {
            var pane_id = pane.get("id"),
                cell;

            this._paneProperties[pane_id].daysInPrevMonth = 0;

            for (cell = 5; cell >= 0; cell--) {
                pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).setContent("&nbsp;");
            }
        },

        /**
         * A render assist method that cleans up the last few cells in the month grid
         * when the number of days in the month changes.
         * @method _cleanUpNextMonthCells
         * @param {Node} pane The calendar pane that needs the last date cells cleaned up.
         * @private
         */
        _cleanUpNextMonthCells : function (pane) {
            var pane_id = pane.get("id");
                pane.one("#" + pane_id + "_6_29").removeClass(CAL_NEXTMONTH_DAY);
                pane.one("#" + pane_id + "_7_30").removeClass(CAL_NEXTMONTH_DAY);
                pane.one("#" + pane_id + "_8_31").removeClass(CAL_NEXTMONTH_DAY);
                pane.one("#" + pane_id + "_0_30").removeClass(CAL_NEXTMONTH_DAY);
                pane.one("#" + pane_id + "_1_31").removeClass(CAL_NEXTMONTH_DAY);
        },

        /**
         * A render assist method that turns on the view of the next month's dates
         * in a given calendar pane.
         * @method _turnNextMonthOn
         * @param {Node} pane The calendar pane that needs its next month's dates view
         * modified.
         * @protected
         */
        _turnNextMonthOn : function (pane) {
            var dayCounter = 1,
                pane_id = pane.get("id"),
                daysInMonth = this._paneProperties[pane_id].daysInMonth,
                cutoffCol = this._paneProperties[pane_id].cutoffCol,
                cell,
                startingCell;

            for (cell = daysInMonth - 22; cell < cutoffCol + 7; cell++) {
                pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
            }

            startingCell = cutoffCol;

            if (daysInMonth === 31 && (cutoffCol <= 1)) {
                startingCell = 2;
            } else if (daysInMonth === 30 && cutoffCol === 0) {
                startingCell = 1;
            }

            for (cell = startingCell ; cell < cutoffCol + 7; cell++) {
                pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
            }
        },

        /**
         * A render assist method that turns off the view of the next month's dates
         * in a given calendar pane.
         * @method _turnNextMonthOff
         * @param {Node} pane The calendar pane that needs its next month's dates view
         * modified.
         * @protected
         */
        _turnNextMonthOff : function (pane) {
                var pane_id = pane.get("id"),
                    daysInMonth = this._paneProperties[pane_id].daysInMonth,
                    cutoffCol = this._paneProperties[pane_id].cutoffCol,
                    cell,
                    startingCell;

                for (cell = daysInMonth - 22; cell <= 12; cell++) {
                    pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).setContent("&nbsp;").addClass(CAL_NEXTMONTH_DAY);
                }

                startingCell = 0;

                if (daysInMonth === 31 && (cutoffCol <= 1)) {
                    startingCell = 2;
                } else if (daysInMonth === 30 && cutoffCol === 0) {
                    startingCell = 1;
                }

                for (cell = startingCell ; cell <= 12; cell++) {
                    pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).setContent("&nbsp;").addClass(CAL_NEXTMONTH_DAY);
                }
        },

        /**
         * The handler for the change in the showNextMonth attribute.
         * @method _afterShowNextMonthChange
         * @private
         */
        _afterShowNextMonthChange : function () {

            var contentBox = this.get('contentBox'),
                lastPane = contentBox.one("#" + this._calendarId + "_pane_" + (this._paneNumber - 1));

            this._cleanUpNextMonthCells(lastPane);

            if (this.get('showNextMonth')) {
                this._turnNextMonthOn(lastPane);
            } else {
                this._turnNextMonthOff(lastPane);
            }

        },

        /**
         * The handler for the change in the showPrevMonth attribute.
         * @method _afterShowPrevMonthChange
         * @private
         */
        _afterShowPrevMonthChange : function () {
            var contentBox = this.get('contentBox'),
                firstPane = contentBox.one("#" + this._calendarId + "_pane_" + 0);

            if (this.get('showPrevMonth')) {
                this._turnPrevMonthOn(firstPane);
            } else {
                this._turnPrevMonthOff(firstPane);
            }

        },

         /**
         * The handler for the change in the headerRenderer attribute.
         * @method _afterHeaderRendererChange
         * @private
         */
        _afterHeaderRendererChange : function () {
            var headerCell = this.get("contentBox").one("." + CAL_HD_LABEL);
            headerCell.setContent(this._updateCalendarHeader(this.get('date')));
        },

         /**
         * The handler for the change in the customRenderer attribute.
         * @method _afterCustomRendererChange
         * @private
         */
        _afterCustomRendererChange : function () {
            this._restoreModifiedCells();
            this._renderCustomRules();
        },

         /**
         * The handler for the change in the date attribute. Modifies the calendar
         * view by shifting the calendar grid mask and running custom rendering and
         * selection rendering as necessary.
         * @method _afterDateChange
         * @private
         */
        _afterDateChange : function () {

            var contentBox = this.get('contentBox'),
                headerCell = contentBox.one("." + CAL_HD).one("." + CAL_HD_LABEL),
                calendarPanes = contentBox.all("." + CAL_GRID),
                currentDate = this.get("date"),
                counter = 0;

            contentBox.setStyle("visibility", "hidden");
            headerCell.setContent(this._updateCalendarHeader(currentDate));

            this._restoreModifiedCells();

            calendarPanes.each(function (curNode) {
                this._rerenderCalendarPane(ydate.addMonths(currentDate, counter++), curNode);
            }, this);

            this._afterShowPrevMonthChange();
            this._afterShowNextMonthChange();

            this._renderCustomRules();
            this._renderSelectedDates();

            contentBox.setStyle("visibility", "visible");
        },

         /**
         * A rendering assist method that initializes the HTML for a single
         * calendar pane.
         * @method _initCalendarPane
         * @param {Date} baseDate The date corresponding to the month of the given
         * calendar pane.
         * @param {String} pane_id The id of the pane, to be used as a prefix for
         * element ids in the given pane.
         * @private
         */
        _initCalendarPane : function (baseDate, pane_id) {
            // Get a list of short weekdays from the internationalization package, or else use default English ones.
            var weekdays = this.get('strings.very_short_weekdays') || ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
                fullweekdays = this.get('strings.weekdays') || ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
                // Get the first day of the week from the internationalization package, or else use Sunday as default.
                firstday = this.get('strings.first_weekday') || 0,
                // Compute the cutoff column of the masked calendar table, based on the start date and the first day of week.
                cutoffCol = this._getCutoffColumn(baseDate, firstday),
                // Compute the number of days in the month based on starting date
                daysInMonth = ydate.daysInMonth(baseDate),
                // Initialize the array of individual row HTML strings
                row_array = ['','','','','',''],
                // Initialize the partial templates object
                partials = {},

                day,
                row,
                column,
                date,
                id_date,
                calendar_day_class,
                column_visibility,
                output;

                // Initialize the partial template for the weekday row cells.
                partials.weekday_row = '';

            // Populate the partial template for the weekday row cells with weekday names
            for (day = firstday; day <= firstday + 6; day++) {
                partials.weekday_row +=
                    substitute(CalendarBase.WEEKDAY_TEMPLATE, {
                        weekdayname: weekdays[day%7],
                        full_weekdayname: fullweekdays[day%7]
                    });
            }

            // Populate the partial template for the weekday row container with the weekday row cells
            partials.weekday_row_template = substitute(CalendarBase.WEEKDAY_ROW_TEMPLATE, partials);

            // Populate the array of individual row HTML strings
            for (row = 0; row <= 5; row++) {

                for (column = 0; column <= 12; column++) {

                    // Compute the value of the date that needs to populate the cell
                    date = 7*row - 5 + column;

                    // Compose the value of the unique id of the current calendar cell
                    id_date = pane_id + "_" + column + "_" + date;

                    // Set the calendar day class to one of three possible values
                    calendar_day_class = CAL_DAY;

                    if (date < 1) {
                        calendar_day_class = CAL_PREVMONTH_DAY;
                    } else if (date > daysInMonth) {
                        calendar_day_class = CAL_NEXTMONTH_DAY;
                    }

                    // Cut off dates that fall before the first and after the last date of the month
                    if (date < 1 || date > daysInMonth) {
                        date = "&nbsp;";
                    }

                    // Decide on whether a column in the masked table is visible or not based on the value of the cutoff column.
                    column_visibility = (column >= cutoffCol && column < (cutoffCol + 7)) ? '' : CAL_COL_HIDDEN;

                    // Substitute the values into the partial calendar day template and add it to the current row HTML string
                    row_array[row] += substitute (CalendarBase.CALDAY_TEMPLATE, {
                        day_content: date,
                        calendar_col_class: "calendar_col" + column,
                        calendar_col_visibility_class: column_visibility,
                        calendar_day_class: calendar_day_class,
                        calendar_day_id: id_date
                    });
                }
            }

            // Instantiate the partial calendar pane body template
            partials.body_template = '';

            // Populate the body template with the rows templates
            arrayEach (row_array, function (v) {
                 partials.body_template += substitute(CalendarBase.CALDAY_ROW_TEMPLATE, {calday_row: v});
            });

            // Populate the calendar grid id
            partials.calendar_pane_id = pane_id;

            // Populate the calendar pane tabindex
            partials.calendar_pane_tabindex = this.get("tabIndex");
            partials.pane_arialabel = ydate.format(baseDate, { format: "%B %Y" });

            // Generate final output by substituting class names.
            output = substitute(substitute (CalendarBase.CALENDAR_GRID_TEMPLATE, partials),
                                                            CalendarBase.CALENDAR_STRINGS);

            // Store the initialized pane information
            this._paneProperties[pane_id] = {cutoffCol: cutoffCol, daysInMonth: daysInMonth, paneDate: baseDate};

            return output;
        },

         /**
         * A rendering assist method that rerenders a specified calendar pane, based
         * on a new Date.
         * @method _rerenderCalendarPane
         * @param {Date} newDate The date corresponding to the month of the given
         * calendar pane.
         * @param {Node} pane The node corresponding to the calendar pane to be rerenders.
         * @private
         */
        _rerenderCalendarPane : function (newDate, pane) {

            // Get the first day of the week from the internationalization package, or else use Sunday as default.
            var firstday = this.get('strings.first_weekday') || 0,
                // Compute the cutoff column of the masked calendar table, based on the start date and the first day of week.
                cutoffCol = this._getCutoffColumn(newDate, firstday),
                // Compute the number of days in the month based on starting date
                daysInMonth = ydate.daysInMonth(newDate),
                // Get pane id for easier reference
                paneId = pane.get("id"),
                column,
                currentColumn,
                curCell;

            // Hide the pane before making DOM changes to speed them up
            pane.setStyle("visibility", "hidden");
            pane.setAttribute("aria-label", ydate.format(newDate, {format:"%B %Y"}));

            // Go through all columns, and flip their visibility setting based on whether they are within the unmasked range.
            for (column = 0; column <= 12; column++) {
                currentColumn = pane.all("." + "calendar_col" + column);
                currentColumn.removeClass(CAL_COL_HIDDEN);

                if (column < cutoffCol || column >= (cutoffCol + 7)) {
                    currentColumn.addClass(CAL_COL_HIDDEN);
                } else {
                    // Clean up dates in visible columns to account for the correct number of days in a month
                    switch(column) {
                        case 0:
                            curCell = pane.one("#" + paneId + "_0_30");
                            if (daysInMonth >= 30) {
                                curCell.set("text", "30");
                                curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
                            } else {
                                curCell.setContent("&nbsp;");
                                curCell.addClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
                            }
                            break;
                        case 1:
                            curCell = pane.one("#" + paneId + "_1_31");
                            if (daysInMonth >= 31) {
                                curCell.set("text", "31");
                                curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
                            } else {
                                curCell.setContent("&nbsp;");
                                curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
                            }
                            break;
                        case 6:
                            curCell = pane.one("#" + paneId + "_6_29");
                            if (daysInMonth >= 29) {
                                curCell.set("text", "29");
                                curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
                            } else {
                                curCell.setContent("&nbsp;");
                                curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
                            }
                            break;
                        case 7:
                            curCell = pane.one("#" + paneId + "_7_30");
                            if (daysInMonth >= 30) {
                                curCell.set("text", "30");
                                curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
                            } else {
                                curCell.setContent("&nbsp;");
                                curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
                            }
                            break;
                        case 8:
                            curCell = pane.one("#" + paneId + "_8_31");
                            if (daysInMonth >= 31) {
                                curCell.set("text", "31");
                                curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
                            } else {
                                curCell.setContent("&nbsp;");
                                curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
                            }
                            break;
                    }
                }
            }

            // Update stored pane properties
            this._paneProperties[paneId].cutoffCol = cutoffCol;
            this._paneProperties[paneId].daysInMonth = daysInMonth;
            this._paneProperties[paneId].paneDate = newDate;

            // Bring the pane visibility back after all DOM changes are done
            pane.setStyle("visibility", "visible");

        },

         /**
         * A rendering assist method that updates the calendar header based
         * on a given date and potentially the provided headerRenderer.
         * @method _updateCalendarHeader
         * @param {Date} baseDate The date with which to update the calendar header.
         * @private
         */
        _updateCalendarHeader : function (baseDate) {
            var headerString = "",
                headerRenderer = this.get("headerRenderer");

            if (Y.Lang.isString(headerRenderer)) {
                headerString = ydate.format(baseDate, {format:headerRenderer});
            } else if (headerRenderer instanceof Function) {
                headerString = headerRenderer.call(this, baseDate);
            }

            return headerString;
        },

         /**
         * A rendering assist method that initializes the calendar header HTML
         * based on a given date and potentially the provided headerRenderer.
         * @method _updateCalendarHeader
         * @param {Date} baseDate The date with which to initialize the calendar header.
         * @private
         */
        _initCalendarHeader : function (baseDate) {
            return substitute(substitute(CalendarBase.HEADER_TEMPLATE, {
                    calheader: this._updateCalendarHeader(baseDate),
                    calendar_id: this._calendarId
                }), CalendarBase.CALENDAR_STRINGS );
        },

         /**
         * A rendering assist method that initializes the calendar HTML
         * based on a given date.
         * @method _initCalendarHTML
         * @param {Date} baseDate The date with which to initialize the calendar.
         * @private
         */
        _initCalendarHTML : function (baseDate) {
            // Instantiate the partials holder
            var partials = {},
                // Counter for iterative template replacement.
                counter = 0,
                singlePane,
                output;

            // Generate the template for the header
            partials.header_template =  this._initCalendarHeader(baseDate);
            partials.calendar_id = this._calendarId;

            partials.body_template = substitute(substitute (CalendarBase.CONTENT_TEMPLATE, partials),
                                                                                     CalendarBase.CALENDAR_STRINGS);

            // Instantiate the iterative template replacer function
            function paneReplacer () {
                singlePane = this._initCalendarPane(ydate.addMonths(baseDate, counter), partials.calendar_id + "_pane_" + counter);
                counter++;
                return singlePane;
            }

            // Go through all occurrences of the calendar_grid_template token and replace it with an appropriate calendar grid.
            output = partials.body_template.replace(/\{calendar_grid_template\}/g, Y.bind(paneReplacer, this));

            // Update the paneNumber count
            this._paneNumber = counter;

            return output;
        }
    }, {

         /**
            * The CSS classnames for the calendar templates.
            * @property CALENDAR_STRINGS
            * @type Object
            * @readOnly
            * @protected
            * @static
            */
        CALENDAR_STRINGS: {
            calendar_grid_class       : CAL_GRID,
            calendar_body_class       : CAL_BODY,
            calendar_hd_class         : CAL_HD,
            calendar_hd_label_class   : CAL_HD_LABEL,
            calendar_weekdayrow_class : CAL_WDAYROW,
            calendar_weekday_class    : CAL_WDAY,
            calendar_row_class        : CAL_ROW,
            calendar_day_class        : CAL_DAY,
            calendar_dayanchor_class  : CAL_ANCHOR,
            calendar_pane_class       : CAL_PANE,
            calendar_right_grid_class : CAL_RIGHT_GRID,
            calendar_left_grid_class  : CAL_LEFT_GRID,
            calendar_status_class     : CAL_STATUS
        },

        /*

        ARIA_STATUS_TEMPLATE: '<div role="status" aria-atomic="true" class="{calendar_status_class}"></div>',

        AriaStatus : null,

        updateStatus : function (statusString) {

            if (!CalendarBase.AriaStatus) {
                CalendarBase.AriaStatus = create(
                                                             substitute (CalendarBase.ARIA_STATUS_TEMPLATE,
                                                                                     CalendarBase.CALENDAR_STRINGS));
                Y.one("body").append(CalendarBase.AriaStatus);
            }

                CalendarBase.AriaStatus.set("text", statusString);
        },

        */

         /**
            * The main content template for calendar.
            * @property CONTENT_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        CONTENT_TEMPLATE:  '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
                            '{header_template}' +
                            '<div class="yui3-u-1">' +
                            '{calendar_grid_template}' +
                            '</div>' +
                            '</div>',

         /**
            * A single pane template for calendar (same as default CONTENT_TEMPLATE)
            * @property ONE_PANE_TEMPLATE
            * @type String
            * @protected
            * @readOnly
            * @static
            */
        ONE_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
                                '{header_template}' +
                                '<div class="yui3-u-1">' +
                                    '{calendar_grid_template}' +
                                '</div>' +
                            '</div>',

         /**
            * A two pane template for calendar.
            * @property TWO_PANE_TEMPLATE
            * @type String
            * @protected
            * @readOnly
            * @static
            */
        TWO_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
                                '{header_template}' +
                                '<div class="yui3-u-1-2">'+
                                    '<div class = "{calendar_left_grid_class}">' +
                                        '{calendar_grid_template}' +
                                    '</div>' +
                                '</div>' +
                                '<div class="yui3-u-1-2">' +
                                    '<div class = "{calendar_right_grid_class}">' +
                                        '{calendar_grid_template}' +
                                    '</div>' +
                                '</div>' +
                            '</div>',
         /**
            * A three pane template for calendar.
            * @property THREE_PANE_TEMPLATE
            * @type String
            * @protected
            * @readOnly
            * @static
            */
        THREE_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
                                '{header_template}' +
                                '<div class="yui3-u-1-3">' +
                                    '<div class="{calendar_left_grid_class}">' +
                                        '{calendar_grid_template}' +
                                    '</div>' +
                                '</div>' +
                                '<div class="yui3-u-1-3">' +
                                    '{calendar_grid_template}' +
                                '</div>' +
                                '<div class="yui3-u-1-3">' +
                                    '<div class="{calendar_right_grid_class}">' +
                                        '{calendar_grid_template}' +
                                    '</div>' +
                                '</div>' +
                            '</div>',
         /**
            * A template for the calendar grid.
            * @property CALENDAR_GRID_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        CALENDAR_GRID_TEMPLATE: '<table class="{calendar_grid_class}" id="{calendar_pane_id}" role="grid" aria-readonly="true" ' +
                                    'aria-label="{pane_arialabel}" tabindex="{calendar_pane_tabindex}">' +
                                    '<thead>' +
                                        '{weekday_row_template}' +
                                    '</thead>' +
                                    '<tbody>' +
                                        '{body_template}' +
                                    '</tbody>' +
                                '</table>',

         /**
            * A template for the calendar header.
            * @property HEADER_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        HEADER_TEMPLATE: '<div class="yui3-g {calendar_hd_class}">' +
                            '<div class="yui3-u {calendar_hd_label_class}" id="{calendar_id}_header" aria-role="heading">' +
                                '{calheader}' +
                            '</div>' +
                        '</div>',

         /**
            * A template for the row of weekday names.
            * @property WEEKDAY_ROW_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        WEEKDAY_ROW_TEMPLATE: '<tr class="{calendar_weekdayrow_class}" role="row">' +
                                '{weekday_row}' +
                            '</tr>',

         /**
            * A template for a single row of calendar days.
            * @property CALDAY_ROW_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        CALDAY_ROW_TEMPLATE: '<tr class="{calendar_row_class}" role="row">' +
                                '{calday_row}' +
                            '</tr>',

         /**
            * A template for a single cell with a weekday name.
            * @property CALDAY_ROW_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        WEEKDAY_TEMPLATE: '<th class="{calendar_weekday_class}" role="columnheader" aria-label="{full_weekdayname}">{weekdayname}</th>',

         /**
            * A template for a single cell with a calendar day.
            * @property CALDAY_TEMPLATE
            * @type String
            * @protected
            * @static
            */
        CALDAY_TEMPLATE: '<td class="{calendar_col_class} {calendar_day_class} {calendar_col_visibility_class}" id="{calendar_day_id}" ' +
                            'role="gridcell" tabindex="-1">' +
                            '{day_content}' +
                        '</td>',

         /**
            * The identity of the widget.
            *
            * @property NAME
            * @type String
            * @default 'calendarBase'
            * @readOnly
            * @protected
            * @static
            */
        NAME: 'calendarBase',

         /**
            * Static property used to define the default attribute configuration of
            * the Widget.
            *
            * @property ATTRS
            * @type {Object}
            * @protected
            * @static
            */
        ATTRS: {
            tabIndex: {
                value: 1
            },
            /**
             * The date corresponding to the current calendar view. Always
             * normalized to the first of the month that contains the date
             * at assignment time. Used as the first date visible in the
             * calendar.
             *
             * @attribute date
             * @type Date
             * @default The first of the month containing today's date, as
             * set on the end user's system.
             */
            date: {
                value: new Date(),
                setter: function (val) {
                    var newDate = this._normalizeDate(val);
                    if (ydate.areEqual(newDate, this.get('date'))) {
                            return this.get('date');
                    } else {
                            return newDate;
                    }
                }
            },

            /**
             * A setting specifying whether to shows days from the previous
             * month in the visible month's grid, if there are empty preceding
             * cells available.
             *
             * @attribute showPrevMonth
             * @type boolean
             * @default false
             */
            showPrevMonth: {
                value: false
            },

            /**
             * A setting specifying whether to shows days from the next
             * month in the visible month's grid, if there are empty
             * cells available at the end.
             *
             * @attribute showNextMonth
             * @type boolean
             * @default false
             */
            showNextMonth: {
                value: false
            },

            /**
             * Strings and properties derived from the internationalization packages
             * for the calendar.
             *
             * @attribute strings
             * @type Object
             * @protected
             */
            strings : {
                valueFn: function() { return Y.Intl.get("calendar-base"); }
            },

            /**
             * Custom header renderer for the calendar.
             *
             * @attribute headerRenderer
             * @type String | Function
             */
            headerRenderer: {
                value: "%B %Y"
            },

            /**
             * The name of the rule which all enabled dates should match.
             * Either disabledDatesRule or enabledDatesRule should be specified,
             * or neither, but not both.
             *
             * @attribute enabledDatesRule
             * @type String
             * @default null
             */
            enabledDatesRule: {
                value: null
            },

            /**
             * The name of the rule which all disabled dates should match.
             * Either disabledDatesRule or enabledDatesRule should be specified,
             * or neither, but not both.
             *
             * @attribute disabledDatesRule
             * @type String
             * @default null
             */
            disabledDatesRule: {
                value: null
            },

            /**
             * A read-only attribute providing a list of currently selected dates.
             *
             * @attribute selectedDates
             * @readOnly
             * @type Array
             */
            selectedDates : {
                readOnly: true,
                getter: function () {
                    return (this._getSelectedDatesList());
                }
            },

            /**
             * An object of the form {rules:Object, filterFunction:Function},
             * providing  set of rules and a custom rendering function for
             * customizing specific calendar cells.
             *
             * @attribute customRenderer
             * @readOnly
             * @type Object
             * @default {}
             */
            customRenderer : {
                lazyAdd: false,
                value: {},
                setter: function (val) {
                    this._rules = val.rules;
                    this._filterFunction = val.filterFunction;
                }
            }
        }

    });