src/cm/media/js/lib/yui/yui3-3.15.0/build/calendar-base/calendar-base.js
changeset 602 e16a97fb364a
equal deleted inserted replaced
601:d334a616c023 602:e16a97fb364a
       
     1 YUI.add('calendar-base', function (Y, NAME) {
       
     2 
       
     3 /**
       
     4  * The CalendarBase submodule is a basic UI calendar view that displays
       
     5  * a range of dates in a two-dimensional month grid, with one or more
       
     6  * months visible at a single time. CalendarBase supports custom date
       
     7  * rendering, multiple calendar panes, and selection.
       
     8  * @module calendar
       
     9  * @submodule calendar-base
       
    10  */
       
    11 
       
    12 var getCN                 = Y.ClassNameManager.getClassName,
       
    13     CALENDAR              = 'calendar',
       
    14     CAL_GRID              = getCN(CALENDAR, 'grid'),
       
    15     CAL_LEFT_GRID         = getCN(CALENDAR, 'left-grid'),
       
    16     CAL_RIGHT_GRID        = getCN(CALENDAR, 'right-grid'),
       
    17     CAL_BODY              = getCN(CALENDAR, 'body'),
       
    18     CAL_HD                = getCN(CALENDAR, 'header'),
       
    19     CAL_HD_LABEL          = getCN(CALENDAR, 'header-label'),
       
    20     CAL_WDAYROW           = getCN(CALENDAR, 'weekdayrow'),
       
    21     CAL_WDAY              = getCN(CALENDAR, 'weekday'),
       
    22     CAL_COL_HIDDEN        = getCN(CALENDAR, 'column-hidden'),
       
    23     CAL_DAY_SELECTED      = getCN(CALENDAR, 'day-selected'),
       
    24     SELECTION_DISABLED    = getCN(CALENDAR, 'selection-disabled'),
       
    25     CAL_ROW               = getCN(CALENDAR, 'row'),
       
    26     CAL_DAY               = getCN(CALENDAR, 'day'),
       
    27     CAL_PREVMONTH_DAY     = getCN(CALENDAR, 'prevmonth-day'),
       
    28     CAL_NEXTMONTH_DAY     = getCN(CALENDAR, 'nextmonth-day'),
       
    29     CAL_ANCHOR            = getCN(CALENDAR, 'anchor'),
       
    30     CAL_PANE              = getCN(CALENDAR, 'pane'),
       
    31     CAL_STATUS            = getCN(CALENDAR, 'status'),
       
    32     L           = Y.Lang,
       
    33     substitute  = L.sub,
       
    34     arrayEach   = Y.Array.each,
       
    35     objEach     = Y.Object.each,
       
    36     iOf         = Y.Array.indexOf,
       
    37     hasKey      = Y.Object.hasKey,
       
    38     setVal      = Y.Object.setValue,
       
    39     isEmpty     = Y.Object.isEmpty,
       
    40     ydate       = Y.DataType.Date;
       
    41 
       
    42 /** Create a calendar view to represent a single or multiple
       
    43     * month range of dates, rendered as a grid with date and
       
    44     * weekday labels.
       
    45     *
       
    46     * @class CalendarBase
       
    47     * @extends Widget
       
    48     * @param config {Object} Configuration object (see Configuration
       
    49     * attributes)
       
    50     * @constructor
       
    51     */
       
    52 function CalendarBase() {
       
    53     CalendarBase.superclass.constructor.apply ( this, arguments );
       
    54 }
       
    55 
       
    56 
       
    57 
       
    58 Y.CalendarBase = Y.extend( CalendarBase, Y.Widget, {
       
    59 
       
    60     /**
       
    61      * A storage for various properties of individual month
       
    62      * panes.
       
    63      *
       
    64      * @property _paneProperties
       
    65      * @type Object
       
    66      * @private
       
    67      */
       
    68     _paneProperties : {},
       
    69 
       
    70     /**
       
    71      * The number of month panes in the calendar, deduced
       
    72      * from the CONTENT_TEMPLATE's number of {calendar_grid}
       
    73      * tokens.
       
    74      *
       
    75      * @property _paneNumber
       
    76      * @type Number
       
    77      * @private
       
    78      */
       
    79     _paneNumber : 1,
       
    80 
       
    81     /**
       
    82      * The unique id used to prefix various elements of this
       
    83      * calendar instance.
       
    84      *
       
    85      * @property _calendarId
       
    86      * @type String
       
    87      * @private
       
    88      */
       
    89     _calendarId : null,
       
    90 
       
    91     /**
       
    92      * The hash map of selected dates, populated with
       
    93      * selectDates() and deselectDates() methods
       
    94      *
       
    95      * @property _selectedDates
       
    96      * @type Object
       
    97      * @private
       
    98      */
       
    99     _selectedDates : {},
       
   100 
       
   101     /**
       
   102      * A private copy of the rules object, populated
       
   103      * by setting the customRenderer attribute.
       
   104      *
       
   105      * @property _rules
       
   106      * @type Object
       
   107      * @private
       
   108      */
       
   109     _rules : {},
       
   110 
       
   111     /**
       
   112      * A private copy of the filterFunction, populated
       
   113      * by setting the customRenderer attribute.
       
   114      *
       
   115      * @property _filterFunction
       
   116      * @type Function
       
   117      * @private
       
   118      */
       
   119     _filterFunction : null,
       
   120 
       
   121     /**
       
   122      * Storage for calendar cells modified by any custom
       
   123      * formatting. The storage is cleared, used to restore
       
   124      * cells to the original state, and repopulated accordingly
       
   125      * when the calendar is rerendered.
       
   126      *
       
   127      * @property _storedDateCells
       
   128      * @type Object
       
   129      * @private
       
   130      */
       
   131     _storedDateCells : {},
       
   132 
       
   133     /**
       
   134      * Designated initializer
       
   135      * Initializes instance-level properties of
       
   136      * calendar.
       
   137      *
       
   138      * @method initializer
       
   139      */
       
   140     initializer : function () {
       
   141         this._paneProperties = {};
       
   142         this._calendarId = Y.guid('calendar');
       
   143         this._selectedDates = {};
       
   144         if (isEmpty(this._rules)) {
       
   145              this._rules = {};
       
   146         }
       
   147         this._storedDateCells = {};
       
   148     },
       
   149 
       
   150     /**
       
   151      * renderUI implementation
       
   152      *
       
   153      * Creates a visual representation of the calendar based on existing parameters.
       
   154      * @method renderUI
       
   155      */
       
   156     renderUI : function () {
       
   157 
       
   158         var contentBox = this.get('contentBox');
       
   159         contentBox.appendChild(this._initCalendarHTML(this.get('date')));
       
   160 
       
   161         if (this.get('showPrevMonth')) {
       
   162                 this._afterShowPrevMonthChange();
       
   163         }
       
   164         if (this.get('showNextMonth')) {
       
   165                 this._afterShowNextMonthChange();
       
   166         }
       
   167 
       
   168         this._renderCustomRules();
       
   169         this._renderSelectedDates();
       
   170 
       
   171         this.get("boundingBox").setAttribute("aria-labelledby", this._calendarId + "_header");
       
   172 
       
   173     },
       
   174 
       
   175     /**
       
   176      * bindUI implementation
       
   177      *
       
   178      * Assigns listeners to relevant events that change the state
       
   179      * of the calendar.
       
   180      * @method bindUI
       
   181      */
       
   182     bindUI : function () {
       
   183         this.after('dateChange', this._afterDateChange);
       
   184         this.after('showPrevMonthChange', this._afterShowPrevMonthChange);
       
   185         this.after('showNextMonthChange', this._afterShowNextMonthChange);
       
   186         this.after('headerRendererChange', this._afterHeaderRendererChange);
       
   187         this.after('customRendererChange', this._afterCustomRendererChange);
       
   188         this.after('enabledDatesRuleChange', this._afterCustomRendererChange);
       
   189         this.after('disabledDatesRuleChange', this._afterCustomRendererChange);
       
   190         this.after('focusedChange', this._afterFocusedChange);
       
   191         this.after('selectionChange', this._renderSelectedDates);
       
   192         this._bindCalendarEvents();
       
   193     },
       
   194 
       
   195 
       
   196     /**
       
   197      * An internal utility method that generates a list of selected dates
       
   198      * from the hash storage.
       
   199      *
       
   200      * @method _getSelectedDatesList
       
   201      * @protected
       
   202      * @return {Array} The array of `Date`s that are currently selected.
       
   203      */
       
   204     _getSelectedDatesList : function () {
       
   205         var output = [];
       
   206 
       
   207         objEach (this._selectedDates, function (year) {
       
   208             objEach (year, function (month) {
       
   209                 objEach (month, function (day) {
       
   210                     output.push (day);
       
   211                 }, this);
       
   212             }, this);
       
   213         }, this);
       
   214 
       
   215         return output;
       
   216     },
       
   217 
       
   218     /**
       
   219      * A utility method that returns all dates selected in a specific month.
       
   220      *
       
   221      * @method _getSelectedDatesInMonth
       
   222      * @param {Date} oDate corresponding to the month for which selected dates
       
   223      * are requested.
       
   224      * @protected
       
   225      * @return {Array} The array of `Date`s in a given month that are currently selected.
       
   226      */
       
   227     _getSelectedDatesInMonth : function (oDate) {
       
   228         var year = oDate.getFullYear(),
       
   229             month = oDate.getMonth();
       
   230 
       
   231         if (hasKey(this._selectedDates, year) && hasKey(this._selectedDates[year], month)) {
       
   232             return Y.Object.values(this._selectedDates[year][month]);
       
   233         } else {
       
   234             return [];
       
   235         }
       
   236     },
       
   237 
       
   238 
       
   239     /**
       
   240      * An internal parsing method that receives a String list of numbers
       
   241      * and number ranges (of the form "1,2,3,4-6,7-9,10,11" etc.) and checks
       
   242      * whether a specific number is included in this list. Used for looking
       
   243      * up dates in the customRenderer rule set.
       
   244      *
       
   245      * @method _isNumInList
       
   246      * @param {Number} num The number to look for in a list.
       
   247      * @param {String} strList The list of numbers of the form "1,2,3,4-6,7-8,9", etc.
       
   248      * @private
       
   249      * @return {boolean} Returns true if the given number is in the given list.
       
   250      */
       
   251     _isNumInList : function (num, strList) {
       
   252         if (strList === "all") {
       
   253             return true;
       
   254         } else {
       
   255             var elements = strList.split(","),
       
   256                 i = elements.length,
       
   257                 range;
       
   258 
       
   259             while (i--) {
       
   260                 range = elements[i].split("-");
       
   261                 if (range.length === 2 && num >= parseInt(range[0], 10) && num <= parseInt(range[1], 10)) {
       
   262                     return true;
       
   263                 }
       
   264                 else if (range.length === 1 && (parseInt(elements[i], 10) === num)) {
       
   265                     return true;
       
   266                 }
       
   267             }
       
   268             return false;
       
   269         }
       
   270     },
       
   271 
       
   272     /**
       
   273      * Given a specific date, returns an array of rules (from the customRenderer rule set)
       
   274      * that the given date matches.
       
   275      *
       
   276      * @method _getRulesForDate
       
   277      * @param {Date} oDate The date for which an array of rules is needed
       
   278      * @private
       
   279      * @return {Array} Returns an array of `String`s, each containg the name of
       
   280      * a rule that the given date matches.
       
   281      */
       
   282     _getRulesForDate : function (oDate) {
       
   283         var year = oDate.getFullYear(),
       
   284                 month = oDate.getMonth(),
       
   285                 date = oDate.getDate(),
       
   286                 wday = oDate.getDay(),
       
   287                 rules = this._rules,
       
   288                 outputRules = [],
       
   289                 years, months, dates, days;
       
   290 
       
   291         for (years in rules) {
       
   292             if (this._isNumInList(year, years)) {
       
   293                 if (L.isString(rules[years])) {
       
   294                         outputRules.push(rules[years]);
       
   295                 }
       
   296                 else {
       
   297                     for (months in rules[years]) {
       
   298                         if (this._isNumInList(month, months)) {
       
   299                             if (L.isString(rules[years][months])) {
       
   300                                     outputRules.push(rules[years][months]);
       
   301                             }
       
   302                             else {
       
   303                                 for (dates in rules[years][months]) {
       
   304                                     if (this._isNumInList(date, dates)) {
       
   305                                         if (L.isString(rules[years][months][dates])) {
       
   306                                                 outputRules.push(rules[years][months][dates]);
       
   307                                         }
       
   308                                         else {
       
   309                                             for (days in rules[years][months][dates]) {
       
   310                                                 if (this._isNumInList(wday, days)) {
       
   311                                                     if (L.isString(rules[years][months][dates][days])) {
       
   312                                                         outputRules.push(rules[years][months][dates][days]);
       
   313                                                     }
       
   314                                                 }
       
   315                                             }
       
   316                                         }
       
   317                                     }
       
   318                                 }
       
   319                             }
       
   320                         }
       
   321                     }
       
   322                 }
       
   323             }
       
   324         }
       
   325         return outputRules;
       
   326     },
       
   327 
       
   328     /**
       
   329      * A utility method which, given a specific date and a name of the rule,
       
   330      * checks whether the date matches the given rule.
       
   331      *
       
   332      * @method _matchesRule
       
   333      * @param {Date} oDate The date to check
       
   334      * @param {String} rule The name of the rule that the date should match.
       
   335      * @private
       
   336      * @return {boolean} Returns true if the date matches the given rule.
       
   337      *
       
   338      */
       
   339     _matchesRule : function (oDate, rule) {
       
   340         return (iOf(this._getRulesForDate(oDate), rule) >= 0);
       
   341     },
       
   342 
       
   343     /**
       
   344      * A utility method which checks whether a given date matches the `enabledDatesRule`
       
   345      * or does not match the `disabledDatesRule` and therefore whether it can be selected.
       
   346      * @method _canBeSelected
       
   347      * @param {Date} oDate The date to check
       
   348      * @private
       
   349      * @return {boolean} Returns true if the date can be selected; false otherwise.
       
   350      */
       
   351     _canBeSelected : function (oDate) {
       
   352 
       
   353         var enabledDatesRule = this.get("enabledDatesRule"),
       
   354             disabledDatesRule = this.get("disabledDatesRule");
       
   355 
       
   356         if (enabledDatesRule) {
       
   357             return this._matchesRule(oDate, enabledDatesRule);
       
   358         } else if (disabledDatesRule) {
       
   359             return !this._matchesRule(oDate, disabledDatesRule);
       
   360         } else {
       
   361             return true;
       
   362         }
       
   363     },
       
   364 
       
   365     /**
       
   366      * Selects a given date or array of dates.
       
   367      * @method selectDates
       
   368      * @param {Date|Array} dates A `Date` or `Array` of `Date`s.
       
   369      * @return {CalendarBase} A reference to this object
       
   370      * @chainable
       
   371      */
       
   372     selectDates : function (dates) {
       
   373         if (ydate.isValidDate(dates)) {
       
   374             this._addDateToSelection(dates);
       
   375         }
       
   376         else if (L.isArray(dates)) {
       
   377             this._addDatesToSelection(dates);
       
   378         }
       
   379         return this;
       
   380     },
       
   381 
       
   382     /**
       
   383      * Deselects a given date or array of dates, or deselects
       
   384      * all dates if no argument is specified.
       
   385      * @method deselectDates
       
   386      * @param {Date|Array} [dates] A `Date` or `Array` of `Date`s, or no
       
   387      * argument if all dates should be deselected.
       
   388      * @return {CalendarBase} A reference to this object
       
   389      * @chainable
       
   390      */
       
   391     deselectDates : function (dates) {
       
   392         if (!dates) {
       
   393             this._clearSelection();
       
   394         }
       
   395         else if (ydate.isValidDate(dates)) {
       
   396             this._removeDateFromSelection(dates);
       
   397         }
       
   398         else if (L.isArray(dates)) {
       
   399             this._removeDatesFromSelection(dates);
       
   400         }
       
   401         return this;
       
   402     },
       
   403 
       
   404     /**
       
   405      * A utility method that adds a given date to selection..
       
   406      * @method _addDateToSelection
       
   407      * @param {Date} oDate The date to add to selection.
       
   408      * @param {Number} [index] An optional parameter that is used
       
   409      * to differentiate between individual date selections and multiple
       
   410      * date selections.
       
   411      * @private
       
   412      */
       
   413     _addDateToSelection : function (oDate, index) {
       
   414         oDate.setHours(12);
       
   415 
       
   416         if (this._canBeSelected(oDate)) {
       
   417 
       
   418             var year = oDate.getFullYear(),
       
   419                 month = oDate.getMonth(),
       
   420                 day = oDate.getDate();
       
   421 
       
   422             if (hasKey(this._selectedDates, year)) {
       
   423                 if (hasKey(this._selectedDates[year], month)) {
       
   424                     this._selectedDates[year][month][day] = oDate;
       
   425                 } else {
       
   426                     this._selectedDates[year][month] = {};
       
   427                     this._selectedDates[year][month][day] = oDate;
       
   428                 }
       
   429             } else {
       
   430                 this._selectedDates[year] = {};
       
   431                 this._selectedDates[year][month] = {};
       
   432                 this._selectedDates[year][month][day] = oDate;
       
   433             }
       
   434 
       
   435             this._selectedDates = setVal(this._selectedDates, [year, month, day], oDate);
       
   436 
       
   437             if (!index) {
       
   438                 this._fireSelectionChange();
       
   439             }
       
   440         }
       
   441     },
       
   442 
       
   443     /**
       
   444      * A utility method that adds a given list of dates to selection.
       
   445      * @method _addDatesToSelection
       
   446      * @param {Array} datesArray The list of dates to add to selection.
       
   447      * @private
       
   448      */
       
   449     _addDatesToSelection : function (datesArray) {
       
   450         arrayEach(datesArray, this._addDateToSelection, this);
       
   451         this._fireSelectionChange();
       
   452     },
       
   453 
       
   454     /**
       
   455      * A utility method that adds a given range of dates to selection.
       
   456      * @method _addDateRangeToSelection
       
   457      * @param {Date} startDate The first date of the given range.
       
   458      * @param {Date} endDate The last date of the given range.
       
   459      * @private
       
   460      */
       
   461     _addDateRangeToSelection : function (startDate, endDate) {
       
   462 
       
   463         var timezoneDifference = (endDate.getTimezoneOffset() - startDate.getTimezoneOffset())*60000,
       
   464             startTime = startDate.getTime(),
       
   465             endTime   = endDate.getTime(),
       
   466             tempTime,
       
   467             time,
       
   468             addedDate;
       
   469 
       
   470         if (startTime > endTime) {
       
   471             tempTime = startTime;
       
   472             startTime = endTime;
       
   473             endTime = tempTime + timezoneDifference;
       
   474         } else {
       
   475             endTime = endTime - timezoneDifference;
       
   476         }
       
   477 
       
   478 
       
   479         for (time = startTime; time <= endTime; time += 86400000) {
       
   480             addedDate = new Date(time);
       
   481             addedDate.setHours(12);
       
   482             this._addDateToSelection(addedDate, time);
       
   483         }
       
   484         this._fireSelectionChange();
       
   485     },
       
   486 
       
   487     /**
       
   488      * A utility method that removes a given date from selection..
       
   489      * @method _removeDateFromSelection
       
   490      * @param {Date} oDate The date to remove from selection.
       
   491      * @param {Number} [index] An optional parameter that is used
       
   492      * to differentiate between individual date selections and multiple
       
   493      * date selections.
       
   494      * @private
       
   495      */
       
   496     _removeDateFromSelection : function (oDate, index) {
       
   497         var year = oDate.getFullYear(),
       
   498             month = oDate.getMonth(),
       
   499             day = oDate.getDate();
       
   500 
       
   501         if (hasKey(this._selectedDates, year) &&
       
   502             hasKey(this._selectedDates[year], month) &&
       
   503             hasKey(this._selectedDates[year][month], day)
       
   504         ) {
       
   505             delete this._selectedDates[year][month][day];
       
   506             if (!index) {
       
   507                 this._fireSelectionChange();
       
   508             }
       
   509         }
       
   510     },
       
   511 
       
   512     /**
       
   513      * A utility method that removes a given list of dates from selection.
       
   514      * @method _removeDatesFromSelection
       
   515      * @param {Array} datesArray The list of dates to remove from selection.
       
   516      * @private
       
   517      */
       
   518     _removeDatesFromSelection : function (datesArray) {
       
   519         arrayEach(datesArray, this._removeDateFromSelection, this);
       
   520         this._fireSelectionChange();
       
   521     },
       
   522 
       
   523     /**
       
   524      * A utility method that removes a given range of dates from selection.
       
   525      * @method _removeDateRangeFromSelection
       
   526      * @param {Date} startDate The first date of the given range.
       
   527      * @param {Date} endDate The last date of the given range.
       
   528      * @private
       
   529      */
       
   530     _removeDateRangeFromSelection : function (startDate, endDate) {
       
   531         var startTime = startDate.getTime(),
       
   532             endTime   = endDate.getTime(),
       
   533             time;
       
   534 
       
   535         for (time = startTime; time <= endTime; time += 86400000) {
       
   536             this._removeDateFromSelection(new Date(time), time);
       
   537         }
       
   538 
       
   539         this._fireSelectionChange();
       
   540     },
       
   541 
       
   542     /**
       
   543      * A utility method that removes all dates from selection.
       
   544      * @method _clearSelection
       
   545      * @param {boolean} noevent A Boolean specifying whether a selectionChange
       
   546      * event should be fired. If true, the event is not fired.
       
   547      * @private
       
   548      */
       
   549     _clearSelection : function (noevent) {
       
   550         this._selectedDates = {};
       
   551         this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
       
   552         if (!noevent) {
       
   553             this._fireSelectionChange();
       
   554         }
       
   555     },
       
   556 
       
   557     /**
       
   558      * A utility method that fires a selectionChange event.
       
   559      * @method _fireSelectionChange
       
   560      * @private
       
   561      */
       
   562     _fireSelectionChange : function () {
       
   563 
       
   564         /**
       
   565         * Fired when the set of selected dates changes. Contains a payload with
       
   566         * a `newSelection` property with an array of selected dates.
       
   567         *
       
   568         * @event selectionChange
       
   569         */
       
   570         this.fire("selectionChange", {newSelection: this._getSelectedDatesList()});
       
   571     },
       
   572 
       
   573     /**
       
   574      * A utility method that restores cells modified by custom formatting.
       
   575      * @method _restoreModifiedCells
       
   576      * @private
       
   577      */
       
   578     _restoreModifiedCells : function () {
       
   579         var contentbox = this.get("contentBox"),
       
   580             id;
       
   581         for (id in this._storedDateCells) {
       
   582             contentbox.one("#" + id).replace(this._storedDateCells[id]);
       
   583             delete this._storedDateCells[id];
       
   584         }
       
   585     },
       
   586 
       
   587     /**
       
   588      * A rendering assist method that renders all cells modified by the customRenderer
       
   589      * rules, as well as the enabledDatesRule and disabledDatesRule.
       
   590      * @method _renderCustomRules
       
   591      * @private
       
   592      */
       
   593     _renderCustomRules : function () {
       
   594 
       
   595         this.get("contentBox").all("." + CAL_DAY + ",." + CAL_NEXTMONTH_DAY).removeClass(SELECTION_DISABLED).setAttribute("aria-disabled", false);
       
   596 
       
   597         if (!isEmpty(this._rules)) {
       
   598             var paneNum,
       
   599                 paneDate,
       
   600                 dateArray;
       
   601 
       
   602             for (paneNum = 0; paneNum < this._paneNumber; paneNum++) {
       
   603                 paneDate = ydate.addMonths(this.get("date"), paneNum);
       
   604                 dateArray = ydate.listOfDatesInMonth(paneDate);
       
   605                 arrayEach(dateArray, Y.bind(this._renderCustomRulesHelper, this));
       
   606             }
       
   607         }
       
   608     },
       
   609 
       
   610     /**
       
   611     * A handler for a date selection event (either a click or a keyboard
       
   612     *   selection) that adds the appropriate CSS class to a specific DOM
       
   613     *   node corresponding to the date and sets its aria-selected
       
   614     *   attribute to true.
       
   615     *
       
   616     * @method _renderCustomRulesHelper
       
   617     * @private
       
   618     */
       
   619     _renderCustomRulesHelper: function (date) {
       
   620         var enRule = this.get("enabledDatesRule"),
       
   621             disRule = this.get("disabledDatesRule"),
       
   622             matchingRules,
       
   623             dateNode;
       
   624 
       
   625         matchingRules = this._getRulesForDate(date);
       
   626         if (matchingRules.length > 0) {
       
   627             if ((enRule && iOf(matchingRules, enRule) < 0) || (!enRule && disRule && iOf(matchingRules, disRule) >= 0)) {
       
   628                 this._disableDate(date);
       
   629             }
       
   630 
       
   631             if (L.isFunction(this._filterFunction)) {
       
   632                 dateNode = this._dateToNode(date);
       
   633                 this._storedDateCells[dateNode.get("id")] = dateNode.cloneNode(true);
       
   634                 this._filterFunction (date, dateNode, matchingRules);
       
   635             }
       
   636         } else if (enRule) {
       
   637             this._disableDate(date);
       
   638         }
       
   639     },
       
   640 
       
   641     /**
       
   642      * A rendering assist method that renders all cells that are currently selected.
       
   643      * @method _renderSelectedDates
       
   644      * @private
       
   645      */
       
   646     _renderSelectedDates : function () {
       
   647         this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
       
   648 
       
   649         var paneNum,
       
   650             paneDate,
       
   651             dateArray;
       
   652 
       
   653         for (paneNum = 0; paneNum < this._paneNumber; paneNum++) {
       
   654             paneDate = ydate.addMonths(this.get("date"), paneNum);
       
   655             dateArray = this._getSelectedDatesInMonth(paneDate);
       
   656 
       
   657             arrayEach(dateArray, Y.bind(this._renderSelectedDatesHelper, this));
       
   658         }
       
   659     },
       
   660 
       
   661     /**
       
   662     * Takes in a date and determines whether that date has any rules
       
   663     *   matching it in the customRenderer; then calls the specified
       
   664     *   filterFunction if that's the case and/or disables the date
       
   665     *   if the rule is specified as a disabledDatesRule.
       
   666     *
       
   667     * @method _renderSelectedDatesHelper
       
   668     * @private
       
   669     */
       
   670     _renderSelectedDatesHelper: function (date) {
       
   671         this._dateToNode(date).addClass(CAL_DAY_SELECTED).setAttribute("aria-selected", true);
       
   672     },
       
   673 
       
   674     /**
       
   675      * Add the selection-disabled class and aria-disabled attribute to a node corresponding
       
   676      * to a given date.
       
   677      *
       
   678      * @method _disableDate
       
   679      * @param {Date} date The date to disable
       
   680      * @private
       
   681      */
       
   682     _disableDate: function (date) {
       
   683        this._dateToNode(date).addClass(SELECTION_DISABLED).setAttribute("aria-disabled", true);
       
   684     },
       
   685 
       
   686     /**
       
   687      * A utility method that converts a date to the node wrapping the calendar cell
       
   688      * the date corresponds to..
       
   689      * @method _dateToNode
       
   690      * @param {Date} oDate The date to convert to Node
       
   691      * @protected
       
   692      * @return {Node} The node wrapping the DOM element of the cell the date
       
   693      * corresponds to.
       
   694      */
       
   695     _dateToNode : function (oDate) {
       
   696         var day = oDate.getDate(),
       
   697             col = 0,
       
   698             daymod = day%7,
       
   699             paneNum = (12 + oDate.getMonth() - this.get("date").getMonth()) % 12,
       
   700             paneId = this._calendarId + "_pane_" + paneNum,
       
   701             cutoffCol = this._paneProperties[paneId].cutoffCol;
       
   702 
       
   703         switch (daymod) {
       
   704             case (0):
       
   705                 if (cutoffCol >= 6) {
       
   706                     col = 12;
       
   707                 } else {
       
   708                     col = 5;
       
   709                 }
       
   710                 break;
       
   711             case (1):
       
   712                     col = 6;
       
   713                 break;
       
   714             case (2):
       
   715                 if (cutoffCol > 0) {
       
   716                     col = 7;
       
   717                 } else {
       
   718                     col = 0;
       
   719                 }
       
   720                 break;
       
   721             case (3):
       
   722                 if (cutoffCol > 1) {
       
   723                     col = 8;
       
   724                 } else {
       
   725                     col = 1;
       
   726                 }
       
   727                 break;
       
   728             case (4):
       
   729                 if (cutoffCol > 2) {
       
   730                     col = 9;
       
   731                 } else {
       
   732                     col = 2;
       
   733                 }
       
   734                 break;
       
   735             case (5):
       
   736                 if (cutoffCol > 3) {
       
   737                     col = 10;
       
   738                 } else {
       
   739                     col = 3;
       
   740                 }
       
   741                 break;
       
   742             case (6):
       
   743                 if (cutoffCol > 4) {
       
   744                     col = 11;
       
   745                 } else {
       
   746                     col = 4;
       
   747                 }
       
   748                 break;
       
   749         }
       
   750         return(this.get("contentBox").one("#" + this._calendarId + "_pane_" + paneNum + "_" + col + "_" + day));
       
   751 
       
   752     },
       
   753 
       
   754     /**
       
   755      * A utility method that converts a node corresponding to the DOM element of
       
   756      * the cell for a particular date to that date.
       
   757      * @method _nodeToDate
       
   758      * @param {Node} oNode The Node wrapping the DOM element of a particular date cell.
       
   759      * @protected
       
   760      * @return {Date} The date corresponding to the DOM element that the given node wraps.
       
   761      */
       
   762     _nodeToDate : function (oNode) {
       
   763 
       
   764         var idParts = oNode.get("id").split("_").reverse(),
       
   765             paneNum = parseInt(idParts[2], 10),
       
   766             day  = parseInt(idParts[0], 10),
       
   767             shiftedDate = ydate.addMonths(this.get("date"), paneNum),
       
   768             year = shiftedDate.getFullYear(),
       
   769             month = shiftedDate.getMonth();
       
   770 
       
   771         return new Date(year, month, day, 12, 0, 0, 0);
       
   772     },
       
   773 
       
   774     /**
       
   775      * A placeholder method, called from bindUI, to bind the Calendar events.
       
   776      * @method _bindCalendarEvents
       
   777      * @protected
       
   778      */
       
   779     _bindCalendarEvents : function () {},
       
   780 
       
   781     /**
       
   782      * A utility method that normalizes a given date by converting it to the 1st
       
   783      * day of the month the date is in, with the time set to noon.
       
   784      * @method _normalizeDate
       
   785      * @param {Date} oDate The date to normalize
       
   786      * @protected
       
   787      * @return {Date} The normalized date, set to the first of the month, with time
       
   788      * set to noon.
       
   789      */
       
   790     _normalizeDate : function (date) {
       
   791         if (date) {
       
   792             return new Date(date.getFullYear(), date.getMonth(), 1, 12, 0, 0, 0);
       
   793         } else {
       
   794             return null;
       
   795         }
       
   796     },
       
   797 
       
   798     /**
       
   799      * A utility method that normalizes a given date by setting its time to noon.
       
   800      * @method _normalizeTime
       
   801      * @param {Date} oDate The date to normalize
       
   802      * @protected
       
   803      * @return {Date} The normalized date
       
   804      * set to noon.
       
   805      */
       
   806     _normalizeTime : function (date) {
       
   807         if (date) {
       
   808             return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 12, 0, 0, 0);
       
   809         } else {
       
   810             return null;
       
   811         }
       
   812     },
       
   813 
       
   814 
       
   815     /**
       
   816      * A render assist utility method that computes the cutoff column for the calendar
       
   817      * rendering mask.
       
   818      * @method _getCutoffColumn
       
   819      * @param {Date} date The date of the month grid to compute the cutoff column for.
       
   820      * @param {Number} firstday The first day of the week (modified by internationalized calendars)
       
   821      * @private
       
   822      * @return {Number} The number of the cutoff column.
       
   823      */
       
   824     _getCutoffColumn : function (date, firstday) {
       
   825         var distance = this._normalizeDate(date).getDay() - firstday,
       
   826             cutOffColumn = 6 - (distance + 7) % 7;
       
   827         return cutOffColumn;
       
   828     },
       
   829 
       
   830     /**
       
   831      * A render assist method that turns on the view of the previous month's dates
       
   832      * in a given calendar pane.
       
   833      * @method _turnPrevMonthOn
       
   834      * @param {Node} pane The calendar pane that needs its previous month's dates view
       
   835      * modified.
       
   836      * @protected
       
   837      */
       
   838     _turnPrevMonthOn : function (pane) {
       
   839         var pane_id = pane.get("id"),
       
   840             pane_date = this._paneProperties[pane_id].paneDate,
       
   841             daysInPrevMonth = ydate.daysInMonth(ydate.addMonths(pane_date, -1)),
       
   842             cell;
       
   843 
       
   844         if (!this._paneProperties[pane_id].hasOwnProperty("daysInPrevMonth")) {
       
   845             this._paneProperties[pane_id].daysInPrevMonth = 0;
       
   846         }
       
   847 
       
   848         if (daysInPrevMonth !== this._paneProperties[pane_id].daysInPrevMonth) {
       
   849 
       
   850             this._paneProperties[pane_id].daysInPrevMonth = daysInPrevMonth;
       
   851 
       
   852             for (cell = 5; cell >= 0; cell--) {
       
   853                 pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).set('text', daysInPrevMonth--);
       
   854             }
       
   855         }
       
   856     },
       
   857 
       
   858     /**
       
   859      * A render assist method that turns off the view of the previous month's dates
       
   860      * in a given calendar pane.
       
   861      * @method _turnPrevMonthOff
       
   862      * @param {Node} pane The calendar pane that needs its previous month's dates view
       
   863      * modified.
       
   864      * @protected
       
   865      */
       
   866     _turnPrevMonthOff : function (pane) {
       
   867         var pane_id = pane.get("id"),
       
   868             cell;
       
   869 
       
   870         this._paneProperties[pane_id].daysInPrevMonth = 0;
       
   871 
       
   872         for (cell = 5; cell >= 0; cell--) {
       
   873             pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).setContent("&nbsp;");
       
   874         }
       
   875     },
       
   876 
       
   877     /**
       
   878      * A render assist method that cleans up the last few cells in the month grid
       
   879      * when the number of days in the month changes.
       
   880      * @method _cleanUpNextMonthCells
       
   881      * @param {Node} pane The calendar pane that needs the last date cells cleaned up.
       
   882      * @private
       
   883      */
       
   884     _cleanUpNextMonthCells : function (pane) {
       
   885         var pane_id = pane.get("id");
       
   886             pane.one("#" + pane_id + "_6_29").removeClass(CAL_NEXTMONTH_DAY);
       
   887             pane.one("#" + pane_id + "_7_30").removeClass(CAL_NEXTMONTH_DAY);
       
   888             pane.one("#" + pane_id + "_8_31").removeClass(CAL_NEXTMONTH_DAY);
       
   889             pane.one("#" + pane_id + "_0_30").removeClass(CAL_NEXTMONTH_DAY);
       
   890             pane.one("#" + pane_id + "_1_31").removeClass(CAL_NEXTMONTH_DAY);
       
   891     },
       
   892 
       
   893     /**
       
   894      * A render assist method that turns on the view of the next month's dates
       
   895      * in a given calendar pane.
       
   896      * @method _turnNextMonthOn
       
   897      * @param {Node} pane The calendar pane that needs its next month's dates view
       
   898      * modified.
       
   899      * @protected
       
   900      */
       
   901     _turnNextMonthOn : function (pane) {
       
   902         var dayCounter = 1,
       
   903             pane_id = pane.get("id"),
       
   904             daysInMonth = this._paneProperties[pane_id].daysInMonth,
       
   905             cutoffCol = this._paneProperties[pane_id].cutoffCol,
       
   906             cell,
       
   907             startingCell;
       
   908 
       
   909         for (cell = daysInMonth - 22; cell < cutoffCol + 7; cell++) {
       
   910             pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
       
   911         }
       
   912 
       
   913         startingCell = cutoffCol;
       
   914 
       
   915         if (daysInMonth === 31 && (cutoffCol <= 1)) {
       
   916             startingCell = 2;
       
   917         } else if (daysInMonth === 30 && cutoffCol === 0) {
       
   918             startingCell = 1;
       
   919         }
       
   920 
       
   921         for (cell = startingCell ; cell < cutoffCol + 7; cell++) {
       
   922             pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
       
   923         }
       
   924     },
       
   925 
       
   926     /**
       
   927      * A render assist method that turns off the view of the next month's dates
       
   928      * in a given calendar pane.
       
   929      * @method _turnNextMonthOff
       
   930      * @param {Node} pane The calendar pane that needs its next month's dates view
       
   931      * modified.
       
   932      * @protected
       
   933      */
       
   934     _turnNextMonthOff : function (pane) {
       
   935             var pane_id = pane.get("id"),
       
   936                 daysInMonth = this._paneProperties[pane_id].daysInMonth,
       
   937                 cutoffCol = this._paneProperties[pane_id].cutoffCol,
       
   938                 cell,
       
   939                 startingCell;
       
   940 
       
   941             for (cell = daysInMonth - 22; cell <= 12; cell++) {
       
   942                 pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).setContent("&nbsp;").addClass(CAL_NEXTMONTH_DAY);
       
   943             }
       
   944 
       
   945             startingCell = 0;
       
   946 
       
   947             if (daysInMonth === 31 && (cutoffCol <= 1)) {
       
   948                 startingCell = 2;
       
   949             } else if (daysInMonth === 30 && cutoffCol === 0) {
       
   950                 startingCell = 1;
       
   951             }
       
   952 
       
   953             for (cell = startingCell ; cell <= 12; cell++) {
       
   954                 pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).setContent("&nbsp;").addClass(CAL_NEXTMONTH_DAY);
       
   955             }
       
   956     },
       
   957 
       
   958     /**
       
   959      * The handler for the change in the showNextMonth attribute.
       
   960      * @method _afterShowNextMonthChange
       
   961      * @private
       
   962      */
       
   963     _afterShowNextMonthChange : function () {
       
   964 
       
   965         var contentBox = this.get('contentBox'),
       
   966             lastPane = contentBox.one("#" + this._calendarId + "_pane_" + (this._paneNumber - 1));
       
   967 
       
   968         this._cleanUpNextMonthCells(lastPane);
       
   969 
       
   970         if (this.get('showNextMonth')) {
       
   971             this._turnNextMonthOn(lastPane);
       
   972         } else {
       
   973             this._turnNextMonthOff(lastPane);
       
   974         }
       
   975 
       
   976     },
       
   977 
       
   978     /**
       
   979      * The handler for the change in the showPrevMonth attribute.
       
   980      * @method _afterShowPrevMonthChange
       
   981      * @private
       
   982      */
       
   983     _afterShowPrevMonthChange : function () {
       
   984         var contentBox = this.get('contentBox'),
       
   985             firstPane = contentBox.one("#" + this._calendarId + "_pane_" + 0);
       
   986 
       
   987         if (this.get('showPrevMonth')) {
       
   988             this._turnPrevMonthOn(firstPane);
       
   989         } else {
       
   990             this._turnPrevMonthOff(firstPane);
       
   991         }
       
   992 
       
   993     },
       
   994 
       
   995      /**
       
   996      * The handler for the change in the headerRenderer attribute.
       
   997      * @method _afterHeaderRendererChange
       
   998      * @private
       
   999      */
       
  1000     _afterHeaderRendererChange : function () {
       
  1001         var headerCell = this.get("contentBox").one("." + CAL_HD_LABEL);
       
  1002         headerCell.setContent(this._updateCalendarHeader(this.get('date')));
       
  1003     },
       
  1004 
       
  1005      /**
       
  1006      * The handler for the change in the customRenderer attribute.
       
  1007      * @method _afterCustomRendererChange
       
  1008      * @private
       
  1009      */
       
  1010     _afterCustomRendererChange : function () {
       
  1011         this._restoreModifiedCells();
       
  1012         this._renderCustomRules();
       
  1013     },
       
  1014 
       
  1015      /**
       
  1016      * The handler for the change in the date attribute. Modifies the calendar
       
  1017      * view by shifting the calendar grid mask and running custom rendering and
       
  1018      * selection rendering as necessary.
       
  1019      * @method _afterDateChange
       
  1020      * @private
       
  1021      */
       
  1022     _afterDateChange : function () {
       
  1023 
       
  1024         var contentBox = this.get('contentBox'),
       
  1025             headerCell = contentBox.one("." + CAL_HD).one("." + CAL_HD_LABEL),
       
  1026             calendarPanes = contentBox.all("." + CAL_GRID),
       
  1027             currentDate = this.get("date"),
       
  1028             counter = 0;
       
  1029 
       
  1030         contentBox.setStyle("visibility", "hidden");
       
  1031         headerCell.setContent(this._updateCalendarHeader(currentDate));
       
  1032 
       
  1033         this._restoreModifiedCells();
       
  1034 
       
  1035         calendarPanes.each(function (curNode) {
       
  1036             this._rerenderCalendarPane(ydate.addMonths(currentDate, counter++), curNode);
       
  1037         }, this);
       
  1038 
       
  1039         this._afterShowPrevMonthChange();
       
  1040         this._afterShowNextMonthChange();
       
  1041 
       
  1042         this._renderCustomRules();
       
  1043         this._renderSelectedDates();
       
  1044 
       
  1045         contentBox.setStyle("visibility", "inherit");
       
  1046     },
       
  1047 
       
  1048 
       
  1049      /**
       
  1050      * A rendering assist method that initializes the HTML for a single
       
  1051      * calendar pane.
       
  1052      * @method _initCalendarPane
       
  1053      * @param {Date} baseDate The date corresponding to the month of the given
       
  1054      * calendar pane.
       
  1055      * @param {String} pane_id The id of the pane, to be used as a prefix for
       
  1056      * element ids in the given pane.
       
  1057      * @private
       
  1058      */
       
  1059     _initCalendarPane : function (baseDate, pane_id) {
       
  1060         // Get a list of short weekdays from the internationalization package, or else use default English ones.
       
  1061         var shortWeekDays = this.get('strings.very_short_weekdays') || ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
       
  1062             weekDays = Y.Intl.get('datatype-date-format').A,
       
  1063             // Get the first day of the week from the internationalization package, or else use Sunday as default.
       
  1064             firstday = this.get('strings.first_weekday') || 0,
       
  1065             // Compute the cutoff column of the masked calendar table, based on the start date and the first day of week.
       
  1066             cutoffCol = this._getCutoffColumn(baseDate, firstday),
       
  1067             // Compute the number of days in the month based on starting date
       
  1068             daysInMonth = ydate.daysInMonth(baseDate),
       
  1069             // Initialize the array of individual row HTML strings
       
  1070             row_array = ['','','','','',''],
       
  1071             // Initialize the partial templates object
       
  1072             partials = {},
       
  1073 
       
  1074             day,
       
  1075             row,
       
  1076             column,
       
  1077             date,
       
  1078             id_date,
       
  1079             calendar_day_class,
       
  1080             column_visibility,
       
  1081             output;
       
  1082 
       
  1083             // Initialize the partial template for the weekday row cells.
       
  1084             partials.weekday_row = '';
       
  1085 
       
  1086         // Populate the partial template for the weekday row cells with weekday names
       
  1087         for (day = firstday; day <= firstday + 6; day++) {
       
  1088             partials.weekday_row +=
       
  1089                 substitute(CalendarBase.WEEKDAY_TEMPLATE, {
       
  1090                     short_weekdayname: shortWeekDays[day%7],
       
  1091                     weekdayname: weekDays[day%7]
       
  1092                 });
       
  1093         }
       
  1094 
       
  1095         // Populate the partial template for the weekday row container with the weekday row cells
       
  1096         partials.weekday_row_template = substitute(CalendarBase.WEEKDAY_ROW_TEMPLATE, partials);
       
  1097 
       
  1098         // Populate the array of individual row HTML strings
       
  1099         for (row = 0; row <= 5; row++) {
       
  1100 
       
  1101             for (column = 0; column <= 12; column++) {
       
  1102 
       
  1103                 // Compute the value of the date that needs to populate the cell
       
  1104                 date = 7*row - 5 + column;
       
  1105 
       
  1106                 // Compose the value of the unique id of the current calendar cell
       
  1107                 id_date = pane_id + "_" + column + "_" + date;
       
  1108 
       
  1109                 // Set the calendar day class to one of three possible values
       
  1110                 calendar_day_class = CAL_DAY;
       
  1111 
       
  1112                 if (date < 1) {
       
  1113                     calendar_day_class = CAL_PREVMONTH_DAY;
       
  1114                 } else if (date > daysInMonth) {
       
  1115                     calendar_day_class = CAL_NEXTMONTH_DAY;
       
  1116                 }
       
  1117 
       
  1118                 // Cut off dates that fall before the first and after the last date of the month
       
  1119                 if (date < 1 || date > daysInMonth) {
       
  1120                     date = "&nbsp;";
       
  1121                 }
       
  1122 
       
  1123                 // Decide on whether a column in the masked table is visible or not based on the value of the cutoff column.
       
  1124                 column_visibility = (column >= cutoffCol && column < (cutoffCol + 7)) ? '' : CAL_COL_HIDDEN;
       
  1125 
       
  1126                 // Substitute the values into the partial calendar day template and add it to the current row HTML string
       
  1127                 row_array[row] += substitute (CalendarBase.CALDAY_TEMPLATE, {
       
  1128                     day_content: date,
       
  1129                     calendar_col_class: "calendar_col" + column,
       
  1130                     calendar_col_visibility_class: column_visibility,
       
  1131                     calendar_day_class: calendar_day_class,
       
  1132                     calendar_day_id: id_date
       
  1133                 });
       
  1134             }
       
  1135         }
       
  1136 
       
  1137         // Instantiate the partial calendar pane body template
       
  1138         partials.body_template = '';
       
  1139 
       
  1140         // Populate the body template with the rows templates
       
  1141         arrayEach (row_array, function (v) {
       
  1142              partials.body_template += substitute(CalendarBase.CALDAY_ROW_TEMPLATE, {calday_row: v});
       
  1143         });
       
  1144 
       
  1145         // Populate the calendar grid id
       
  1146         partials.calendar_pane_id = pane_id;
       
  1147 
       
  1148         // Populate the calendar pane tabindex
       
  1149         partials.calendar_pane_tabindex = this.get("tabIndex");
       
  1150         partials.pane_arialabel = ydate.format(baseDate, { format: "%B %Y" });
       
  1151 
       
  1152 
       
  1153         // Generate final output by substituting class names.
       
  1154         output = substitute(substitute (CalendarBase.CALENDAR_GRID_TEMPLATE, partials),
       
  1155                                                         CalendarBase.CALENDAR_STRINGS);
       
  1156 
       
  1157         // Store the initialized pane information
       
  1158         this._paneProperties[pane_id] = {cutoffCol: cutoffCol, daysInMonth: daysInMonth, paneDate: baseDate};
       
  1159 
       
  1160         return output;
       
  1161     },
       
  1162 
       
  1163      /**
       
  1164      * A rendering assist method that rerenders a specified calendar pane, based
       
  1165      * on a new Date.
       
  1166      * @method _rerenderCalendarPane
       
  1167      * @param {Date} newDate The date corresponding to the month of the given
       
  1168      * calendar pane.
       
  1169      * @param {Node} pane The node corresponding to the calendar pane to be rerenders.
       
  1170      * @private
       
  1171      */
       
  1172     _rerenderCalendarPane : function (newDate, pane) {
       
  1173 
       
  1174         // Get the first day of the week from the internationalization package, or else use Sunday as default.
       
  1175         var firstday = this.get('strings.first_weekday') || 0,
       
  1176             // Compute the cutoff column of the masked calendar table, based on the start date and the first day of week.
       
  1177             cutoffCol = this._getCutoffColumn(newDate, firstday),
       
  1178             // Compute the number of days in the month based on starting date
       
  1179             daysInMonth = ydate.daysInMonth(newDate),
       
  1180             // Get pane id for easier reference
       
  1181             paneId = pane.get("id"),
       
  1182             column,
       
  1183             currentColumn,
       
  1184             curCell;
       
  1185 
       
  1186         // Hide the pane before making DOM changes to speed them up
       
  1187         pane.setStyle("visibility", "hidden");
       
  1188         pane.setAttribute("aria-label", ydate.format(newDate, {format:"%B %Y"}));
       
  1189 
       
  1190         // Go through all columns, and flip their visibility setting based on whether they are within the unmasked range.
       
  1191         for (column = 0; column <= 12; column++) {
       
  1192             currentColumn = pane.all("." + "calendar_col" + column);
       
  1193             currentColumn.removeClass(CAL_COL_HIDDEN);
       
  1194 
       
  1195             if (column < cutoffCol || column >= (cutoffCol + 7)) {
       
  1196                 currentColumn.addClass(CAL_COL_HIDDEN);
       
  1197             } else {
       
  1198                 // Clean up dates in visible columns to account for the correct number of days in a month
       
  1199                 switch(column) {
       
  1200                     case 0:
       
  1201                         curCell = pane.one("#" + paneId + "_0_30");
       
  1202                         if (daysInMonth >= 30) {
       
  1203                             curCell.set("text", "30");
       
  1204                             curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
       
  1205                         } else {
       
  1206                             curCell.setContent("&nbsp;");
       
  1207                             curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
       
  1208                         }
       
  1209                         break;
       
  1210                     case 1:
       
  1211                         curCell = pane.one("#" + paneId + "_1_31");
       
  1212                         if (daysInMonth >= 31) {
       
  1213                             curCell.set("text", "31");
       
  1214                             curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
       
  1215                         } else {
       
  1216                             curCell.setContent("&nbsp;");
       
  1217                             curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
       
  1218                         }
       
  1219                         break;
       
  1220                     case 6:
       
  1221                         curCell = pane.one("#" + paneId + "_6_29");
       
  1222                         if (daysInMonth >= 29) {
       
  1223                             curCell.set("text", "29");
       
  1224                             curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
       
  1225                         } else {
       
  1226                             curCell.setContent("&nbsp;");
       
  1227                             curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
       
  1228                         }
       
  1229                         break;
       
  1230                     case 7:
       
  1231                         curCell = pane.one("#" + paneId + "_7_30");
       
  1232                         if (daysInMonth >= 30) {
       
  1233                             curCell.set("text", "30");
       
  1234                             curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
       
  1235                         } else {
       
  1236                             curCell.setContent("&nbsp;");
       
  1237                             curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
       
  1238                         }
       
  1239                         break;
       
  1240                     case 8:
       
  1241                         curCell = pane.one("#" + paneId + "_8_31");
       
  1242                         if (daysInMonth >= 31) {
       
  1243                             curCell.set("text", "31");
       
  1244                             curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
       
  1245                         } else {
       
  1246                             curCell.setContent("&nbsp;");
       
  1247                             curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
       
  1248                         }
       
  1249                         break;
       
  1250                 }
       
  1251             }
       
  1252         }
       
  1253 
       
  1254         // Update stored pane properties
       
  1255         this._paneProperties[paneId].cutoffCol = cutoffCol;
       
  1256         this._paneProperties[paneId].daysInMonth = daysInMonth;
       
  1257         this._paneProperties[paneId].paneDate = newDate;
       
  1258 
       
  1259         // Bring the pane visibility back after all DOM changes are done
       
  1260         pane.setStyle("visibility", "inherit");
       
  1261 
       
  1262     },
       
  1263 
       
  1264      /**
       
  1265      * A rendering assist method that updates the calendar header based
       
  1266      * on a given date and potentially the provided headerRenderer.
       
  1267      * @method _updateCalendarHeader
       
  1268      * @param {Date} baseDate The date with which to update the calendar header.
       
  1269      * @private
       
  1270      */
       
  1271     _updateCalendarHeader : function (baseDate) {
       
  1272         var headerString = "",
       
  1273             headerRenderer = this.get("headerRenderer");
       
  1274 
       
  1275         if (Y.Lang.isString(headerRenderer)) {
       
  1276             headerString = ydate.format(baseDate, {format:headerRenderer});
       
  1277         } else if (headerRenderer instanceof Function) {
       
  1278             headerString = headerRenderer.call(this, baseDate);
       
  1279         }
       
  1280 
       
  1281         return headerString;
       
  1282     },
       
  1283 
       
  1284      /**
       
  1285      * A rendering assist method that initializes the calendar header HTML
       
  1286      * based on a given date and potentially the provided headerRenderer.
       
  1287      * @method _initCalendarHeader
       
  1288      * @param {Date} baseDate The date with which to initialize the calendar header.
       
  1289      * @private
       
  1290      */
       
  1291     _initCalendarHeader : function (baseDate) {
       
  1292         return substitute(substitute(CalendarBase.HEADER_TEMPLATE, {
       
  1293                 calheader: this._updateCalendarHeader(baseDate),
       
  1294                 calendar_id: this._calendarId
       
  1295             }), CalendarBase.CALENDAR_STRINGS );
       
  1296     },
       
  1297 
       
  1298      /**
       
  1299      * A rendering assist method that initializes the calendar HTML
       
  1300      * based on a given date.
       
  1301      * @method _initCalendarHTML
       
  1302      * @param {Date} baseDate The date with which to initialize the calendar.
       
  1303      * @private
       
  1304      */
       
  1305     _initCalendarHTML : function (baseDate) {
       
  1306         // Instantiate the partials holder
       
  1307         var partials = {},
       
  1308             // Counter for iterative template replacement.
       
  1309             counter = 0,
       
  1310             singlePane,
       
  1311             output;
       
  1312 
       
  1313         // Generate the template for the header
       
  1314         partials.header_template =  this._initCalendarHeader(baseDate);
       
  1315         partials.calendar_id = this._calendarId;
       
  1316 
       
  1317         partials.body_template = substitute(substitute (CalendarBase.CONTENT_TEMPLATE, partials),
       
  1318                                                                                  CalendarBase.CALENDAR_STRINGS);
       
  1319 
       
  1320         // Instantiate the iterative template replacer function
       
  1321         function paneReplacer () {
       
  1322             singlePane = this._initCalendarPane(ydate.addMonths(baseDate, counter), partials.calendar_id + "_pane_" + counter);
       
  1323             counter++;
       
  1324             return singlePane;
       
  1325         }
       
  1326 
       
  1327         // Go through all occurrences of the calendar_grid_template token and replace it with an appropriate calendar grid.
       
  1328         output = partials.body_template.replace(/\{calendar_grid_template\}/g, Y.bind(paneReplacer, this));
       
  1329 
       
  1330         // Update the paneNumber count
       
  1331         this._paneNumber = counter;
       
  1332 
       
  1333         return output;
       
  1334     }
       
  1335 }, {
       
  1336 
       
  1337      /**
       
  1338         * The CSS classnames for the calendar templates.
       
  1339         * @property CALENDAR_STRINGS
       
  1340         * @type Object
       
  1341         * @readOnly
       
  1342         * @protected
       
  1343         * @static
       
  1344         */
       
  1345     CALENDAR_STRINGS: {
       
  1346         calendar_grid_class       : CAL_GRID,
       
  1347         calendar_body_class       : CAL_BODY,
       
  1348         calendar_hd_class         : CAL_HD,
       
  1349         calendar_hd_label_class   : CAL_HD_LABEL,
       
  1350         calendar_weekdayrow_class : CAL_WDAYROW,
       
  1351         calendar_weekday_class    : CAL_WDAY,
       
  1352         calendar_row_class        : CAL_ROW,
       
  1353         calendar_day_class        : CAL_DAY,
       
  1354         calendar_dayanchor_class  : CAL_ANCHOR,
       
  1355         calendar_pane_class       : CAL_PANE,
       
  1356         calendar_right_grid_class : CAL_RIGHT_GRID,
       
  1357         calendar_left_grid_class  : CAL_LEFT_GRID,
       
  1358         calendar_status_class     : CAL_STATUS
       
  1359     },
       
  1360 
       
  1361     /*
       
  1362 
       
  1363     ARIA_STATUS_TEMPLATE: '<div role="status" aria-atomic="true" class="{calendar_status_class}"></div>',
       
  1364 
       
  1365     AriaStatus : null,
       
  1366 
       
  1367     updateStatus : function (statusString) {
       
  1368 
       
  1369         if (!CalendarBase.AriaStatus) {
       
  1370             CalendarBase.AriaStatus = create(
       
  1371                                                          substitute (CalendarBase.ARIA_STATUS_TEMPLATE,
       
  1372                                                                                  CalendarBase.CALENDAR_STRINGS));
       
  1373             Y.one("body").append(CalendarBase.AriaStatus);
       
  1374         }
       
  1375 
       
  1376             CalendarBase.AriaStatus.set("text", statusString);
       
  1377     },
       
  1378 
       
  1379     */
       
  1380 
       
  1381      /**
       
  1382         * The main content template for calendar.
       
  1383         * @property CONTENT_TEMPLATE
       
  1384         * @type String
       
  1385         * @protected
       
  1386         * @static
       
  1387         */
       
  1388     CONTENT_TEMPLATE:  '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
       
  1389                         '{header_template}' +
       
  1390                         '<div class="yui3-u-1">' +
       
  1391                         '{calendar_grid_template}' +
       
  1392                         '</div>' +
       
  1393                         '</div>',
       
  1394 
       
  1395      /**
       
  1396         * A single pane template for calendar (same as default CONTENT_TEMPLATE)
       
  1397         * @property ONE_PANE_TEMPLATE
       
  1398         * @type String
       
  1399         * @protected
       
  1400         * @readOnly
       
  1401         * @static
       
  1402         */
       
  1403     ONE_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
       
  1404                             '{header_template}' +
       
  1405                             '<div class="yui3-u-1">' +
       
  1406                                 '{calendar_grid_template}' +
       
  1407                             '</div>' +
       
  1408                         '</div>',
       
  1409 
       
  1410      /**
       
  1411         * A two pane template for calendar.
       
  1412         * @property TWO_PANE_TEMPLATE
       
  1413         * @type String
       
  1414         * @protected
       
  1415         * @readOnly
       
  1416         * @static
       
  1417         */
       
  1418     TWO_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
       
  1419                             '{header_template}' +
       
  1420                             '<div class="yui3-u-1-2">'+
       
  1421                                 '<div class = "{calendar_left_grid_class}">' +
       
  1422                                     '{calendar_grid_template}' +
       
  1423                                 '</div>' +
       
  1424                             '</div>' +
       
  1425                             '<div class="yui3-u-1-2">' +
       
  1426                                 '<div class = "{calendar_right_grid_class}">' +
       
  1427                                     '{calendar_grid_template}' +
       
  1428                                 '</div>' +
       
  1429                             '</div>' +
       
  1430                         '</div>',
       
  1431      /**
       
  1432         * A three pane template for calendar.
       
  1433         * @property THREE_PANE_TEMPLATE
       
  1434         * @type String
       
  1435         * @protected
       
  1436         * @readOnly
       
  1437         * @static
       
  1438         */
       
  1439     THREE_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
       
  1440                             '{header_template}' +
       
  1441                             '<div class="yui3-u-1-3">' +
       
  1442                                 '<div class="{calendar_left_grid_class}">' +
       
  1443                                     '{calendar_grid_template}' +
       
  1444                                 '</div>' +
       
  1445                             '</div>' +
       
  1446                             '<div class="yui3-u-1-3">' +
       
  1447                                 '{calendar_grid_template}' +
       
  1448                             '</div>' +
       
  1449                             '<div class="yui3-u-1-3">' +
       
  1450                                 '<div class="{calendar_right_grid_class}">' +
       
  1451                                     '{calendar_grid_template}' +
       
  1452                                 '</div>' +
       
  1453                             '</div>' +
       
  1454                         '</div>',
       
  1455      /**
       
  1456         * A template for the calendar grid.
       
  1457         * @property CALENDAR_GRID_TEMPLATE
       
  1458         * @type String
       
  1459         * @protected
       
  1460         * @static
       
  1461         */
       
  1462     CALENDAR_GRID_TEMPLATE: '<table class="{calendar_grid_class}" id="{calendar_pane_id}" role="grid" aria-readonly="true" ' +
       
  1463                                 'aria-label="{pane_arialabel}" tabindex="{calendar_pane_tabindex}">' +
       
  1464                                 '<thead>' +
       
  1465                                     '{weekday_row_template}' +
       
  1466                                 '</thead>' +
       
  1467                                 '<tbody>' +
       
  1468                                     '{body_template}' +
       
  1469                                 '</tbody>' +
       
  1470                             '</table>',
       
  1471 
       
  1472      /**
       
  1473         * A template for the calendar header.
       
  1474         * @property HEADER_TEMPLATE
       
  1475         * @type String
       
  1476         * @protected
       
  1477         * @static
       
  1478         */
       
  1479     HEADER_TEMPLATE: '<div class="yui3-g {calendar_hd_class}">' +
       
  1480                         '<div class="yui3-u {calendar_hd_label_class}" id="{calendar_id}_header" aria-role="heading">' +
       
  1481                             '{calheader}' +
       
  1482                         '</div>' +
       
  1483                     '</div>',
       
  1484 
       
  1485      /**
       
  1486         * A template for the row of weekday names.
       
  1487         * @property WEEKDAY_ROW_TEMPLATE
       
  1488         * @type String
       
  1489         * @protected
       
  1490         * @static
       
  1491         */
       
  1492     WEEKDAY_ROW_TEMPLATE: '<tr class="{calendar_weekdayrow_class}" role="row">' +
       
  1493                             '{weekday_row}' +
       
  1494                         '</tr>',
       
  1495 
       
  1496      /**
       
  1497         * A template for a single row of calendar days.
       
  1498         * @property CALDAY_ROW_TEMPLATE
       
  1499         * @type String
       
  1500         * @protected
       
  1501         * @static
       
  1502         */
       
  1503     CALDAY_ROW_TEMPLATE: '<tr class="{calendar_row_class}" role="row">' +
       
  1504                             '{calday_row}' +
       
  1505                         '</tr>',
       
  1506 
       
  1507      /**
       
  1508         * A template for a single cell with a weekday name.
       
  1509         * @property WEEKDAY_TEMPLATE
       
  1510         * @type String
       
  1511         * @protected
       
  1512         * @static
       
  1513         */
       
  1514     WEEKDAY_TEMPLATE: '<th class="{calendar_weekday_class}" role="columnheader" aria-label="{weekdayname}">{short_weekdayname}</th>',
       
  1515 
       
  1516      /**
       
  1517         * A template for a single cell with a calendar day.
       
  1518         * @property CALDAY_TEMPLATE
       
  1519         * @type String
       
  1520         * @protected
       
  1521         * @static
       
  1522         */
       
  1523     CALDAY_TEMPLATE: '<td class="{calendar_col_class} {calendar_day_class} {calendar_col_visibility_class}" id="{calendar_day_id}" ' +
       
  1524                         'role="gridcell" tabindex="-1">' +
       
  1525                         '{day_content}' +
       
  1526                     '</td>',
       
  1527 
       
  1528      /**
       
  1529         * The identity of the widget.
       
  1530         *
       
  1531         * @property NAME
       
  1532         * @type String
       
  1533         * @default 'calendarBase'
       
  1534         * @readOnly
       
  1535         * @protected
       
  1536         * @static
       
  1537         */
       
  1538     NAME: 'calendarBase',
       
  1539 
       
  1540      /**
       
  1541         * Static property used to define the default attribute configuration of
       
  1542         * the Widget.
       
  1543         *
       
  1544         * @property ATTRS
       
  1545         * @type {Object}
       
  1546         * @protected
       
  1547         * @static
       
  1548         */
       
  1549     ATTRS: {
       
  1550         tabIndex: {
       
  1551             value: 1
       
  1552         },
       
  1553         /**
       
  1554          * The date corresponding to the current calendar view. Always
       
  1555          * normalized to the first of the month that contains the date
       
  1556          * at assignment time. Used as the first date visible in the
       
  1557          * calendar.
       
  1558          *
       
  1559          * @attribute date
       
  1560          * @type Date
       
  1561          * @default The first of the month containing today's date, as
       
  1562          * set on the end user's system.
       
  1563          */
       
  1564         date: {
       
  1565             value: new Date(),
       
  1566             setter: function (val) {
       
  1567                 var newDate = this._normalizeDate(val);
       
  1568                 if (ydate.areEqual(newDate, this.get('date'))) {
       
  1569                         return this.get('date');
       
  1570                 } else {
       
  1571                         return newDate;
       
  1572                 }
       
  1573             }
       
  1574         },
       
  1575 
       
  1576         /**
       
  1577          * A setting specifying whether to shows days from the previous
       
  1578          * month in the visible month's grid, if there are empty preceding
       
  1579          * cells available.
       
  1580          *
       
  1581          * @attribute showPrevMonth
       
  1582          * @type boolean
       
  1583          * @default false
       
  1584          */
       
  1585         showPrevMonth: {
       
  1586             value: false
       
  1587         },
       
  1588 
       
  1589         /**
       
  1590          * A setting specifying whether to shows days from the next
       
  1591          * month in the visible month's grid, if there are empty
       
  1592          * cells available at the end.
       
  1593          *
       
  1594          * @attribute showNextMonth
       
  1595          * @type boolean
       
  1596          * @default false
       
  1597          */
       
  1598         showNextMonth: {
       
  1599             value: false
       
  1600         },
       
  1601 
       
  1602         /**
       
  1603          * Strings and properties derived from the internationalization packages
       
  1604          * for the calendar.
       
  1605          *
       
  1606          * @attribute strings
       
  1607          * @type Object
       
  1608          * @protected
       
  1609          */
       
  1610         strings : {
       
  1611             valueFn: function() { return Y.Intl.get("calendar-base"); }
       
  1612         },
       
  1613 
       
  1614         /**
       
  1615          * Custom header renderer for the calendar.
       
  1616          *
       
  1617          * @attribute headerRenderer
       
  1618          * @type String | Function
       
  1619          */
       
  1620         headerRenderer: {
       
  1621             value: "%B %Y"
       
  1622         },
       
  1623 
       
  1624         /**
       
  1625          * The name of the rule which all enabled dates should match.
       
  1626          * Either disabledDatesRule or enabledDatesRule should be specified,
       
  1627          * or neither, but not both.
       
  1628          *
       
  1629          * @attribute enabledDatesRule
       
  1630          * @type String
       
  1631          * @default null
       
  1632          */
       
  1633         enabledDatesRule: {
       
  1634             value: null
       
  1635         },
       
  1636 
       
  1637         /**
       
  1638          * The name of the rule which all disabled dates should match.
       
  1639          * Either disabledDatesRule or enabledDatesRule should be specified,
       
  1640          * or neither, but not both.
       
  1641          *
       
  1642          * @attribute disabledDatesRule
       
  1643          * @type String
       
  1644          * @default null
       
  1645          */
       
  1646         disabledDatesRule: {
       
  1647             value: null
       
  1648         },
       
  1649 
       
  1650         /**
       
  1651          * A read-only attribute providing a list of currently selected dates.
       
  1652          *
       
  1653          * @attribute selectedDates
       
  1654          * @readOnly
       
  1655          * @type Array
       
  1656          */
       
  1657         selectedDates : {
       
  1658             readOnly: true,
       
  1659             getter: function () {
       
  1660                 return (this._getSelectedDatesList());
       
  1661             }
       
  1662         },
       
  1663 
       
  1664         /**
       
  1665          * An object of the form {rules:Object, filterFunction:Function},
       
  1666          * providing  set of rules and a custom rendering function for
       
  1667          * customizing specific calendar cells.
       
  1668          *
       
  1669          * @attribute customRenderer
       
  1670          * @type Object
       
  1671          * @default {}
       
  1672          */
       
  1673         customRenderer : {
       
  1674             lazyAdd: false,
       
  1675             value: {},
       
  1676             setter: function (val) {
       
  1677                 this._rules = val.rules;
       
  1678                 this._filterFunction = val.filterFunction;
       
  1679             }
       
  1680         }
       
  1681     }
       
  1682 
       
  1683 });
       
  1684 
       
  1685 
       
  1686 }, '@VERSION@', {
       
  1687     "requires": [
       
  1688         "widget",
       
  1689         "datatype-date",
       
  1690         "datatype-date-math",
       
  1691         "cssgrids"
       
  1692     ],
       
  1693     "lang": [
       
  1694         "de",
       
  1695         "en",
       
  1696         "es",
       
  1697         "es-AR",
       
  1698         "fr",
       
  1699         "hu",
       
  1700         "it",
       
  1701         "ja",
       
  1702         "nb-NO",
       
  1703         "nl",
       
  1704         "pt-BR",
       
  1705         "ru",
       
  1706         "zh-Hans",
       
  1707         "zh-Hans-CN",
       
  1708         "zh-Hant",
       
  1709         "zh-Hant-HK",
       
  1710         "zh-HANT-TW"
       
  1711     ],
       
  1712     "skinnable": true
       
  1713 });