diff -r 322d0feea350 -r 89ef5ed3c48b src/cm/media/js/lib/yui/yui_3.10.3/build/calendar-base/calendar-base-debug.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui_3.10.3/build/calendar-base/calendar-base-debug.js Tue Jul 16 14:29:46 2013 +0200 @@ -0,0 +1,1688 @@ +/* +YUI 3.10.3 (build 2fb5187) +Copyright 2013 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +http://yuilibrary.com/license/ +*/ + +YUI.add('calendar-base', function (Y, NAME) { + +/** + * 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(" "); + } + }, + + /** + * 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(" ").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(" ").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 = " "; + } + + // 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(" "); + 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(" "); + 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(" "); + 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(" "); + 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(" "); + 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: '
', + + 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: '