3
Copyright 2012 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('calendar-base', function(Y) {
10
* The CalendarBase submodule is a basic UI calendar view that displays
11
* a range of dates in a two-dimensional month grid, with one or more
12
* months visible at a single time. CalendarBase supports custom date
13
* rendering, multiple calendar panes, and selection.
15
* @submodule calendar-base
18
var getCN = Y.ClassNameManager.getClassName,
19
CALENDAR = 'calendar',
20
CAL_GRID = getCN(CALENDAR, 'grid'),
21
CAL_LEFT_GRID = getCN(CALENDAR, 'left-grid'),
22
CAL_RIGHT_GRID = getCN(CALENDAR, 'right-grid'),
23
CAL_BODY = getCN(CALENDAR, 'body'),
24
CAL_HD = getCN(CALENDAR, 'header'),
25
CAL_HD_LABEL = getCN(CALENDAR, 'header-label'),
26
CAL_WDAYROW = getCN(CALENDAR, 'weekdayrow'),
27
CAL_WDAY = getCN(CALENDAR, 'weekday'),
28
CAL_COL_HIDDEN = getCN(CALENDAR, 'column-hidden'),
29
CAL_DAY_SELECTED = getCN(CALENDAR, 'day-selected'),
30
SELECTION_DISABLED = getCN(CALENDAR, 'selection-disabled'),
31
CAL_ROW = getCN(CALENDAR, 'row'),
32
CAL_DAY = getCN(CALENDAR, 'day'),
33
CAL_PREVMONTH_DAY = getCN(CALENDAR, 'prevmonth-day'),
34
CAL_NEXTMONTH_DAY = getCN(CALENDAR, 'nextmonth-day'),
35
CAL_ANCHOR = getCN(CALENDAR, 'anchor'),
36
CAL_PANE = getCN(CALENDAR, 'pane'),
37
CAL_STATUS = getCN(CALENDAR, 'status'),
41
substitute = Y.substitute,
43
hasVal = Y.Array.hasValue,
44
iOf = Y.Array.indexOf,
45
hasKey = Y.Object.hasKey,
46
setVal = Y.Object.setValue,
48
isEmpty = Y.Object.isEmpty,
49
ydate = Y.DataType.Date;
51
/** Create a calendar view to represent a single or multiple
52
* month range of dates, rendered as a grid with date and
57
* @param config {Object} Configuration object (see Configuration
61
function CalendarBase(config) {
62
CalendarBase.superclass.constructor.apply ( this, arguments );
67
Y.CalendarBase = Y.extend( CalendarBase, Y.Widget, {
70
* A storage for various properties of individual month
73
* @property _paneProperties
80
* The number of month panes in the calendar, deduced
81
* from the CONTENT_TEMPLATE's number of {calendar_grid}
84
* @property _paneNumber
91
* The unique id used to prefix various elements of this
94
* @property _calendarId
101
* The hash map of selected dates, populated with
102
* selectDates() and deselectDates() methods
104
* @property _selectedDates
111
* A private copy of the rules object, populated
112
* by setting the customRenderer attribute.
121
* A private copy of the filterFunction, populated
122
* by setting the customRenderer attribute.
124
* @property _filterFunction
128
_filterFunction : null,
131
* Storage for calendar cells modified by any custom
132
* formatting. The storage is cleared, used to restore
133
* cells to the original state, and repopulated accordingly
134
* when the calendar is rerendered.
136
* @property _storedDateCells
140
_storedDateCells : {},
143
* Designated initializer
144
* Initializes instance-level properties of
147
* @method initializer
149
initializer : function () {
150
this._paneProperties = {};
151
this._calendarId = Y.guid('calendar');
152
this._selectedDates = {};
154
this._storedDateCells = {};
158
* renderUI implementation
160
* Creates a visual representation of the calendar based on existing parameters.
163
renderUI : function () {
165
var contentBox = this.get('contentBox');
166
contentBox.appendChild(this._initCalendarHTML(this.get('date')));
167
if (this.get('showPrevMonth')) {
168
this._afterShowPrevMonthChange();
170
if (this.get('showNextMonth')) {
171
this._afterShowNextMonthChange();
173
this._renderCustomRules();
174
this._renderSelectedDates();
176
this.get("boundingBox").setAttribute("aria-labelledby", this._calendarId + "_header");
180
* bindUI implementation
182
* Assigns listeners to relevant events that change the state
186
bindUI : function () {
187
this.after('dateChange', this._afterDateChange);
188
this.after('showPrevMonthChange', this._afterShowPrevMonthChange);
189
this.after('showNextMonthChange', this._afterShowNextMonthChange);
190
this.after('headerRendererChange', this._afterHeaderRendererChange);
191
this.after('customRendererChange', this._afterCustomRendererChange);
192
this.after('enabledDatesRuleChange', this._afterCustomRendererChange);
193
this.after('disabledDatesRuleChange', this._afterCustomRendererChange);
194
this.after('focusedChange', this._afterFocusedChange);
195
this._bindCalendarEvents();
199
* syncUI implementation
201
* Update the scroll position, based on the current value of scrollY
204
syncUI : function () {
205
if (this.get('showPrevMonth')) {
206
this._afterShowPrevMonthChange();
210
if (this.get('showNextMonth')) {
211
this._afterShowNextMonthChange();
216
* An internal utility method that generates a list of selected dates
217
* from the hash storage.
219
* @method _getSelectedDatesList
221
* @return {Array} The array of `Date`s that are currently selected.
223
_getSelectedDatesList : function () {
225
each (this._selectedDates, function (year) {
226
each (year, function (month) {
227
each (month, function (day) {
236
* A utility method that returns all dates selected in a specific month.
238
* @method _getSelectedDatesInMonth
239
* @param {Date} oDate corresponding to the month for which selected dates
242
* @return {Array} The array of `Date`s in a given month that are currently selected.
244
_getSelectedDatesInMonth : function (oDate) {
245
var year = oDate.getFullYear(),
246
month = oDate.getMonth();
248
if (hasKey(this._selectedDates, year) && hasKey(this._selectedDates[year], month)) {
249
return Y.Object.values(this._selectedDates[year][month]);
257
* An internal rendering method that modifies a date cell to have the
258
* selected CSS class if the date cell is visible.
260
* @method _renderSelectedDate
261
* @param {Date} oDate The date corresponding to a specific date cell.
264
_renderSelectedDate : function (oDate) {
265
if (this._isDateVisible(oDate)) {
266
this._dateToNode(oDate).addClass(CAL_DAY_SELECTED).setAttribute("aria-selected", true);
271
* An internal rendering method that modifies a date cell to remove the
272
* selected CSS class if the date cell is visible.
274
* @method _renderUnelectedDate
275
* @param {Date} oDate The date corresponding to a specific date cell.
278
_renderUnselectedDate : function (oDate) {
279
if (this._isDateVisible(oDate)) {
280
this._dateToNode(oDate).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
285
* An internal utility method that checks whether a particular date
286
* is in the current view of the calendar.
288
* @method _isDateVisible
289
* @param {Date} oDate The date corresponding to a specific date cell.
291
* @return {boolean} Returns true if the given date is in the current
292
* view of the calendar.
294
_isDateVisible : function (oDate) {
295
var minDate = this.get("date"),
296
maxDate = ydate.addMonths(minDate, this._paneNumber - 1),
297
oDateTime = this._normalizeDate(oDate).getTime();
299
if (minDate.getTime() <= oDateTime && oDateTime <= maxDate) {
308
* An internal parsing method that receives a String list of numbers
309
* and number ranges (of the form "1,2,3,4-6,7-9,10,11" etc.) and checks
310
* whether a specific number is included in this list. Used for looking
311
* up dates in the customRenderer rule set.
313
* @method _isNumInList
314
* @param {Number} num The number to look for in a list.
315
* @param {String} strList The list of numbers of the form "1,2,3,4-6,7-8,9", etc.
317
* @return {boolean} Returns true if the given number is in the given list.
319
_isNumInList : function (num, strList) {
320
if (strList == "all") {
324
var elements = strList.split(","),
328
var range = elements[i].split("-");
329
if (range.length == 2 && num >= parseInt(range[0], 10) && num <= parseInt(range[1], 10)) {
332
else if (range.length == 1 && (parseInt(elements[i], 10) == num)) {
341
* Given a specific date, returns an array of rules (from the customRenderer rule set)
342
* that the given date matches.
344
* @method _getRulesForDate
345
* @param {Date} oDate The date for which an array of rules is needed
347
* @return {Array} Returns an array of `String`s, each containg the name of
348
* a rule that the given date matches.
350
_getRulesForDate : function (oDate) {
351
var year = oDate.getFullYear(),
352
month = oDate.getMonth(),
353
date = oDate.getDate(),
354
wday = oDate.getDay(),
357
years, months, dates, days;
359
for (years in rules) {
360
if (this._isNumInList(year, years)) {
361
if (L.isString(rules[years])) {
362
outputRules.push(rules[years]);
365
for (months in rules[years]) {
366
if (this._isNumInList(month, months)) {
367
if (L.isString(rules[years][months])) {
368
outputRules.push(rules[years][months]);
371
for (dates in rules[years][months]) {
372
if (this._isNumInList(date, dates)) {
373
if (L.isString(rules[years][months][dates])) {
374
outputRules.push(rules[years][months][dates]);
377
for (days in rules[years][months][dates]) {
378
if (this._isNumInList(wday, days)) {
379
if (L.isString(rules[years][months][dates][days])) {
380
outputRules.push(rules[years][months][dates][days]);
397
* A utility method which, given a specific date and a name of the rule,
398
* checks whether the date matches the given rule.
400
* @method _matchesRule
401
* @param {Date} oDate The date to check
402
* @param {String} rule The name of the rule that the date should match.
404
* @return {boolean} Returns true if the date matches the given rule.
407
_matchesRule : function (oDate, rule) {
408
return (iOf(this._getRulesForDate(oDate), rule) >= 0);
412
* A utility method which checks whether a given date matches the `enabledDatesRule`
413
* or does not match the `disabledDatesRule` and therefore whether it can be selected.
414
* @method _canBeSelected
415
* @param {Date} oDate The date to check
417
* @return {boolean} Returns true if the date can be selected; false otherwise.
419
_canBeSelected : function (oDate) {
421
var enabledDatesRule = this.get("enabledDatesRule"),
422
disabledDatesRule = this.get("disabledDatesRule");
424
if (enabledDatesRule) {
425
return this._matchesRule(oDate, enabledDatesRule);
427
else if (disabledDatesRule) {
428
return !this._matchesRule(oDate, disabledDatesRule);
436
* Selects a given date or array of dates.
437
* @method selectDates
438
* @param {Date|Array} dates A `Date` or `Array` of `Date`s.
440
selectDates : function (dates) {
441
if (ydate.isValidDate(dates)) {
442
this._addDateToSelection(dates);
444
else if (L.isArray(dates)) {
445
this._addDatesToSelection(dates);
450
* Deselects a given date or array of dates, or deselects
451
* all dates if no argument is specified.
452
* @method deselectDates
453
* @param {Date|Array} [dates] A `Date` or `Array` of `Date`s, or no
454
* argument if all dates should be deselected.
456
deselectDates : function (dates) {
458
this._clearSelection();
460
else if (ydate.isValidDate(dates)) {
461
this._removeDateFromSelection(dates);
463
else if (L.isArray(dates)) {
464
this._removeDatesFromSelection(dates);
469
* A utility method that adds a given date to selection..
470
* @method _addDateToSelection
471
* @param {Date} oDate The date to add to selection.
472
* @param {Number} [index] An optional parameter that is used
473
* to differentiate between individual date selections and multiple
477
_addDateToSelection : function (oDate, index) {
479
if (this._canBeSelected(oDate)) {
481
var year = oDate.getFullYear(),
482
month = oDate.getMonth(),
483
day = oDate.getDate();
485
if (hasKey(this._selectedDates, year)) {
486
if (hasKey(this._selectedDates[year], month)) {
487
this._selectedDates[year][month][day] = oDate;
490
this._selectedDates[year][month] = {};
491
this._selectedDates[year][month][day] = oDate;
495
this._selectedDates[year] = {};
496
this._selectedDates[year][month] = {};
497
this._selectedDates[year][month][day] = oDate;
500
this._selectedDates = setVal(this._selectedDates, [year, month, day], oDate);
501
this._renderSelectedDate(oDate);
504
this._fireSelectionChange();
510
* A utility method that adds a given list of dates to selection.
511
* @method _addDatesToSelection
512
* @param {Array} datesArray The list of dates to add to selection.
515
_addDatesToSelection : function (datesArray) {
516
each(datesArray, this._addDateToSelection, this);
517
this._fireSelectionChange();
521
* A utility method that adds a given range of dates to selection.
522
* @method _addDateRangeToSelection
523
* @param {Date} startDate The first date of the given range.
524
* @param {Date} endDate The last date of the given range.
527
_addDateRangeToSelection : function (startDate, endDate) {
528
var timezoneDifference = (endDate.getTimezoneOffset() - startDate.getTimezoneOffset())*60000,
529
startTime = startDate.getTime(),
530
endTime = endDate.getTime();
532
if (startTime > endTime) {
533
var tempTime = startTime;
535
endTime = tempTime + timezoneDifference;
538
endTime = endTime - timezoneDifference;
542
for (var time = startTime; time <= endTime; time += 86400000) {
543
var addedDate = new Date(time);
544
addedDate.setHours(12);
545
this._addDateToSelection(addedDate, time);
547
this._fireSelectionChange();
551
* A utility method that removes a given date from selection..
552
* @method _removeDateFromSelection
553
* @param {Date} oDate The date to remove from selection.
554
* @param {Number} [index] An optional parameter that is used
555
* to differentiate between individual date selections and multiple
559
_removeDateFromSelection : function (oDate, index) {
560
var year = oDate.getFullYear(),
561
month = oDate.getMonth(),
562
day = oDate.getDate();
563
if (hasKey(this._selectedDates, year) &&
564
hasKey(this._selectedDates[year], month) &&
565
hasKey(this._selectedDates[year][month], day)) {
566
delete this._selectedDates[year][month][day];
567
this._renderUnselectedDate(oDate);
569
this._fireSelectionChange();
575
* A utility method that removes a given list of dates from selection.
576
* @method _removeDatesFromSelection
577
* @param {Array} datesArray The list of dates to remove from selection.
580
_removeDatesFromSelection : function (datesArray) {
581
each(datesArray, this._removeDateDromSelection);
582
this._fireSelectionChange();
586
* A utility method that removes a given range of dates from selection.
587
* @method _removeDateRangeFromSelection
588
* @param {Date} startDate The first date of the given range.
589
* @param {Date} endDate The last date of the given range.
592
_removeDateRangeFromSelection : function (startDate, endDate) {
593
var startTime = startDate.getTime(),
594
endTime = endDate.getTime();
596
for (var time = startTime; time <= endTime; time += 86400000) {
597
this._removeDateFromSelection(new Date(time), time);
600
this._fireSelectionChange();
604
* A utility method that removes all dates from selection.
605
* @method _clearSelection
606
* @param {boolean} noevent A Boolean specifying whether a selectionChange
607
* event should be fired.
610
_clearSelection : function (noevent) {
611
this._selectedDates = {};
612
this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
614
this._fireSelectionChange();
619
* A utility method that fires a selectionChange event.
620
* @method _fireSelectionChange
623
_fireSelectionChange : function () {
626
* Fired when the set of selected dates changes. Contains a payload with
627
* a `newSelection` property with an array of selected dates.
629
* @event selectionChange
631
this.fire("selectionChange", {newSelection: this._getSelectedDatesList()});
635
* A utility method that restores cells modified by custom formatting.
636
* @method _restoreModifiedCells
639
_restoreModifiedCells : function () {
640
var contentbox = this.get("contentBox"),
642
for (id in this._storedDateCells) {
643
contentbox.one("#" + id).replace(this._storedDateCells[id]);
644
delete this._storedDateCells[id];
649
* A rendering assist method that renders all cells modified by the customRenderer
650
* rules, as well as the enabledDatesRule and disabledDatesRule.
651
* @method _renderCustomRules
654
_renderCustomRules : function () {
656
this.get("contentBox").all("." + CAL_DAY + ",." + CAL_NEXTMONTH_DAY).removeClass(SELECTION_DISABLED).setAttribute("aria-disabled", false);
658
if (!isEmpty(this._rules)) {
659
var enRule = this.get("enabledDatesRule"),
660
disRule = this.get("disabledDatesRule");
662
for (var paneNum = 0; paneNum < this._paneNumber; paneNum++) {
663
var paneDate = ydate.addMonths(this.get("date"), paneNum);
664
var dateArray = ydate.listOfDatesInMonth(paneDate);
665
each(dateArray, function (date) {
666
var matchingRules = this._getRulesForDate(date);
667
if (matchingRules.length > 0) {
668
var dateNode = this._dateToNode(date);
669
if ((enRule && iOf(matchingRules, enRule) < 0) || (!enRule && disRule && iOf(matchingRules, disRule) >= 0)) {
670
dateNode.addClass(SELECTION_DISABLED).setAttribute("aria-disabled", true);
673
if (L.isFunction(this._filterFunction)) {
674
this._storedDateCells[dateNode.get("id")] = dateNode.cloneNode(true);
675
this._filterFunction (date, dateNode, matchingRules);
679
var dateNode = this._dateToNode(date);
680
dateNode.addClass(SELECTION_DISABLED).setAttribute("aria-disabled", true);
689
* A rendering assist method that renders all cells that are currently selected.
690
* @method _renderSelectedDates
693
_renderSelectedDates : function () {
694
this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
696
for (var paneNum = 0; paneNum < this._paneNumber; paneNum++) {
697
var paneDate = ydate.addMonths(this.get("date"), paneNum);
698
var dateArray = this._getSelectedDatesInMonth(paneDate);
699
each(dateArray, function (date) {
700
this._dateToNode(date).addClass(CAL_DAY_SELECTED).setAttribute("ari-selected", true);
707
* A utility method that converts a date to the node wrapping the calendar cell
708
* the date corresponds to..
709
* @method _dateToNode
710
* @param {Date} oDate The date to convert to Node
712
* @return {Node} The node wrapping the DOM element of the cell the date
715
_dateToNode : function (oDate) {
716
var day = oDate.getDate(),
719
paneNum = (12 + oDate.getMonth() - this.get("date").getMonth()) % 12,
720
paneId = this._calendarId + "_pane_" + paneNum,
721
cutoffCol = this._paneProperties[paneId].cutoffCol;
725
if (cutoffCol >= 6) {
776
return(this.get("contentBox").one("#" + this._calendarId + "_pane_" + paneNum + "_" + col + "_" + day));
781
* A utility method that converts a node corresponding to the DOM element of
782
* the cell for a particular date to that date.
783
* @method _nodeToDate
784
* @param {Node} oNode The Node wrapping the DOM element of a particular date cell.
786
* @return {Date} The date corresponding to the DOM element that the given node wraps.
788
_nodeToDate : function (oNode) {
790
var idParts = oNode.get("id").split("_").reverse(),
791
paneNum = parseInt(idParts[2], 10),
792
day = parseInt(idParts[0], 10);
794
var shiftedDate = ydate.addMonths(this.get("date"), paneNum),
795
year = shiftedDate.getFullYear(),
796
month = shiftedDate.getMonth();
798
return new Date(year, month, day, 12, 0, 0, 0);
802
* A placeholder method, called from bindUI, to bind the Calendar events.
803
* @method _bindCalendarEvents
806
_bindCalendarEvents : function () {
811
* A utility method that normalizes a given date by converting it to the 1st
812
* day of the month the date is in, with the time set to noon.
813
* @method _normalizeDate
814
* @param {Date} oDate The date to normalize
816
* @return {Date} The normalized date, set to the first of the month, with time
819
_normalizeDate : function (date) {
821
return new Date(date.getFullYear(), date.getMonth(), 1, 12, 0, 0, 0);
830
* A render assist utility method that computes the cutoff column for the calendar
832
* @method _getCutoffColumn
833
* @param {Date} date The date of the month grid to compute the cutoff column for.
834
* @param {Number} firstday The first day of the week (modified by internationalized calendars)
836
* @return {Number} The number of the cutoff column.
838
_getCutoffColumn : function (date, firstday) {
840
var distance = this._normalizeDate(date).getDay() - firstday;
841
var cutOffColumn = 6 - (distance + 7)%7;
847
* A render assist method that turns on the view of the previous month's dates
848
* in a given calendar pane.
849
* @method _turnPrevMonthOn
850
* @param {Node} pane The calendar pane that needs its previous month's dates view
854
_turnPrevMonthOn : function (pane) {
856
var pane_id = pane.get("id"),
857
pane_date = this._paneProperties[pane_id].paneDate,
858
daysInPrevMonth = ydate.daysInMonth(ydate.addMonths(pane_date, -1));
860
if (!this._paneProperties[pane_id].hasOwnProperty("daysInPrevMonth")) {
861
this._paneProperties[pane_id].daysInPrevMonth = 0;
864
if (daysInPrevMonth != this._paneProperties[pane_id].daysInPrevMonth) {
866
this._paneProperties[pane_id].daysInPrevMonth = daysInPrevMonth;
868
for (var cell = 5; cell >= 0; cell--)
870
pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).set('text', daysInPrevMonth--);
879
* A render assist method that turns off the view of the previous month's dates
880
* in a given calendar pane.
881
* @method _turnPrevMonthOff
882
* @param {Node} pane The calendar pane that needs its previous month's dates view
886
_turnPrevMonthOff : function (pane) {
887
var pane_id = pane.get("id");
888
this._paneProperties[pane_id].daysInPrevMonth = 0;
890
for (var cell = 5; cell >= 0; cell--)
892
pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).setContent(" ");
897
* A render assist method that cleans up the last few cells in the month grid
898
* when the number of days in the month changes.
899
* @method _cleanUpNextMonthCells
900
* @param {Node} pane The calendar pane that needs the last date cells cleaned up.
903
_cleanUpNextMonthCells : function (pane) {
904
var pane_id = pane.get("id");
905
pane.one("#" + pane_id + "_6_29").removeClass(CAL_NEXTMONTH_DAY);
906
pane.one("#" + pane_id + "_7_30").removeClass(CAL_NEXTMONTH_DAY);
907
pane.one("#" + pane_id + "_8_31").removeClass(CAL_NEXTMONTH_DAY);
908
pane.one("#" + pane_id + "_0_30").removeClass(CAL_NEXTMONTH_DAY);
909
pane.one("#" + pane_id + "_1_31").removeClass(CAL_NEXTMONTH_DAY);
913
* A render assist method that turns on the view of the next month's dates
914
* in a given calendar pane.
915
* @method _turnNextMonthOn
916
* @param {Node} pane The calendar pane that needs its next month's dates view
920
_turnNextMonthOn : function (pane) {
922
pane_id = pane.get("id"),
923
daysInMonth = this._paneProperties[pane_id].daysInMonth,
924
cutoffCol = this._paneProperties[pane_id].cutoffCol;
926
for (var cell = daysInMonth - 22; cell < cutoffCol + 7; cell++)
928
pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
931
var startingCell = cutoffCol;
932
if (daysInMonth == 31 && (cutoffCol <= 1)) {
935
else if (daysInMonth == 30 && cutoffCol === 0) {
939
for (var cell = startingCell ; cell < cutoffCol + 7; cell++) {
940
pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
945
* A render assist method that turns off the view of the next month's dates
946
* in a given calendar pane.
947
* @method _turnNextMonthOff
948
* @param {Node} pane The calendar pane that needs its next month's dates view
952
_turnNextMonthOff : function (pane) {
953
var pane_id = pane.get("id"),
954
daysInMonth = this._paneProperties[pane_id].daysInMonth,
955
cutoffCol = this._paneProperties[pane_id].cutoffCol;
957
for (var cell = daysInMonth - 22; cell <= 12; cell++)
959
pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).setContent(" ").addClass(CAL_NEXTMONTH_DAY);
962
var startingCell = 0;
963
if (daysInMonth == 31 && (cutoffCol <= 1)) {
966
else if (daysInMonth == 30 && cutoffCol === 0) {
970
for (var cell = startingCell ; cell <= 12; cell++) {
971
pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).setContent(" ").addClass(CAL_NEXTMONTH_DAY);
976
* The handler for the change in the showNextMonth attribute.
977
* @method _afterShowNextMonthChange
980
_afterShowNextMonthChange : function () {
982
var contentBox = this.get('contentBox'),
983
lastPane = contentBox.one("#" + this._calendarId + "_pane_" + (this._paneNumber - 1));
984
this._cleanUpNextMonthCells(lastPane);
987
if (this.get('showNextMonth')) {
988
this._turnNextMonthOn(lastPane);
992
this._turnNextMonthOff(lastPane);
998
* The handler for the change in the showPrevMonth attribute.
999
* @method _afterShowPrevMonthChange
1002
_afterShowPrevMonthChange : function () {
1003
var contentBox = this.get('contentBox'),
1004
firstPane = contentBox.one("#" + this._calendarId + "_pane_" + 0);
1006
if (this.get('showPrevMonth')) {
1007
this._turnPrevMonthOn(firstPane);
1011
this._turnPrevMonthOff(firstPane);
1017
* The handler for the change in the headerRenderer attribute.
1018
* @method _afterHeaderRendererChange
1021
_afterHeaderRendererChange : function () {
1022
var headerCell = this.get("contentBox").one("." + CAL_HD_LABEL);
1023
headerCell.setContent(this._updateCalendarHeader(this.get('date')));
1027
* The handler for the change in the customRenderer attribute.
1028
* @method _afterCustomRendererChange
1031
_afterCustomRendererChange : function () {
1032
this._restoreModifiedCells();
1033
this._renderCustomRules();
1037
* The handler for the change in the date attribute. Modifies the calendar
1038
* view by shifting the calendar grid mask and running custom rendering and
1039
* selection rendering as necessary.
1040
* @method _afterDateChange
1043
_afterDateChange : function () {
1045
var contentBox = this.get('contentBox'),
1046
headerCell = contentBox.one("." + CAL_HD).one("." + CAL_HD_LABEL),
1047
calendarPanes = contentBox.all("." + CAL_GRID),
1048
currentDate = this.get("date"),
1051
contentBox.setStyle("visibility", "hidden");
1052
headerCell.setContent(this._updateCalendarHeader(currentDate));
1054
this._restoreModifiedCells();
1056
calendarPanes.each(function (curNode) {
1057
this._rerenderCalendarPane(ydate.addMonths(currentDate, counter++),
1061
this._afterShowPrevMonthChange();
1062
this._afterShowNextMonthChange();
1064
this._renderCustomRules();
1065
this._renderSelectedDates();
1067
contentBox.setStyle("visibility", "visible");
1072
* A rendering assist method that initializes the HTML for a single
1074
* @method _initCalendarPane
1075
* @param {Date} baseDate The date corresponding to the month of the given
1077
* @param {String} pane_id The id of the pane, to be used as a prefix for
1078
* element ids in the given pane.
1081
_initCalendarPane : function (baseDate, pane_id) {
1082
// Initialize final output HTML string
1084
// Get a list of short weekdays from the internationalization package, or else use default English ones.
1085
weekdays = this.get('strings.very_short_weekdays') || ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
1086
fullweekdays = this.get('strings.weekdays') || ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
1087
// Get the first day of the week from the internationalization package, or else use Sunday as default.
1088
firstday = this.get('strings.first_weekday') || 0,
1089
// Compute the cutoff column of the masked calendar table, based on the start date and the first day of week.
1090
cutoffCol = this._getCutoffColumn(baseDate, firstday),
1091
// Compute the number of days in the month based on starting date
1092
daysInMonth = ydate.daysInMonth(baseDate),
1093
// Initialize the array of individual row HTML strings
1094
row_array = ['','','','','',''],
1095
// Initialize the partial templates object
1098
// Initialize the partial template for the weekday row cells.
1099
partials["weekday_row"] = '';
1101
// Populate the partial template for the weekday row cells with weekday names
1102
for (var day = firstday; day <= firstday + 6; day++) {
1103
partials["weekday_row"] +=
1104
substitute(CalendarBase.WEEKDAY_TEMPLATE,
1105
{weekdayname: weekdays[day%7],
1106
full_weekdayname: fullweekdays[day%7]});
1109
// Populate the partial template for the weekday row container with the weekday row cells
1110
partials["weekday_row_template"] = substitute(CalendarBase.WEEKDAY_ROW_TEMPLATE, partials);
1112
// Populate the array of individual row HTML strings
1113
for (var row = 0; row <= 5; row++) {
1115
for (var column = 0; column <= 12; column++) {
1117
// Compute the value of the date that needs to populate the cell
1118
var date = 7*row - 5 + column;
1120
// Compose the value of the unique id of the current calendar cell
1121
var id_date = pane_id + "_" + column + "_" + date;
1123
// Set the calendar day class to one of three possible values
1124
var calendar_day_class = CAL_DAY;
1127
calendar_day_class = CAL_PREVMONTH_DAY;
1129
else if (date > daysInMonth) {
1130
calendar_day_class = CAL_NEXTMONTH_DAY;
1133
// Cut off dates that fall before the first and after the last date of the month
1134
if (date < 1 || date > daysInMonth) {
1138
// Decide on whether a column in the masked table is visible or not based on the value of the cutoff column.
1139
var column_visibility = (column >= cutoffCol && column < (cutoffCol + 7)) ? '' : CAL_COL_HIDDEN;
1141
// Substitute the values into the partial calendar day template and add it to the current row HTML string
1142
row_array[row] += substitute (CalendarBase.CALDAY_TEMPLATE,
1144
calendar_col_class: "calendar_col" + column,
1145
calendar_col_visibility_class: column_visibility,
1146
calendar_day_class: calendar_day_class,
1147
calendar_day_id: id_date});
1151
// Instantiate the partial calendar pane body template
1152
partials["body_template"] = '';
1154
// Populate the body template with the rows templates
1155
each (row_array, function (v) {
1156
partials["body_template"] += substitute(CalendarBase.CALDAY_ROW_TEMPLATE,
1160
// Populate the calendar grid id
1161
partials["calendar_pane_id"] = pane_id;
1163
// Populate the calendar pane tabindex
1164
partials["calendar_pane_tabindex"] = this.get("tabIndex");
1165
partials["pane_arialabel"] = ydate.format(baseDate, {format:"%B %Y"});
1168
// Generate final output by substituting class names.
1169
var output = substitute(substitute (CalendarBase.CALENDAR_GRID_TEMPLATE, partials),
1170
CalendarBase.CALENDAR_STRINGS);
1172
// Store the initialized pane information
1174
this._paneProperties[pane_id] = {cutoffCol: cutoffCol, daysInMonth: daysInMonth, paneDate: baseDate};
1180
* A rendering assist method that rerenders a specified calendar pane, based
1182
* @method _rerenderCalendarPane
1183
* @param {Date} newDate The date corresponding to the month of the given
1185
* @param {Node} pane The node corresponding to the calendar pane to be rerenders.
1188
_rerenderCalendarPane : function (newDate, pane) {
1190
// Get the first day of the week from the internationalization package, or else use Sunday as default.
1191
var firstday = this.get('strings.first_weekday') || 0,
1192
// Compute the cutoff column of the masked calendar table, based on the start date and the first day of week.
1193
cutoffCol = this._getCutoffColumn(newDate, firstday),
1194
// Compute the number of days in the month based on starting date
1195
daysInMonth = ydate.daysInMonth(newDate),
1196
// Get pane id for easier reference
1197
paneId = pane.get("id");
1199
// Hide the pane before making DOM changes to speed them up
1200
pane.setStyle("visibility", "hidden");
1201
pane.setAttribute("aria-label", ydate.format(newDate, {format:"%B %Y"}));
1203
// Go through all columns, and flip their visibility setting based on whether they are within the unmasked range.
1204
for (var column = 0; column <= 12; column++) {
1205
var currentColumn = pane.all("." + "calendar_col" + column);
1206
currentColumn.removeClass(CAL_COL_HIDDEN);
1208
if (column < cutoffCol || column >= (cutoffCol + 7)) {
1209
currentColumn.addClass(CAL_COL_HIDDEN);
1212
// Clean up dates in visible columns to account for the correct number of days in a month
1216
var curCell = pane.one("#" + paneId + "_0_30");
1217
if (daysInMonth >= 30) {
1218
curCell.set("text", "30");
1219
curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1222
curCell.setContent(" ");
1223
curCell.addClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1227
var curCell = pane.one("#" + paneId + "_1_31");
1228
if (daysInMonth >= 31) {
1229
curCell.set("text", "31");
1230
curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1233
curCell.setContent(" ");
1234
curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1238
var curCell = pane.one("#" + paneId + "_6_29");
1239
if (daysInMonth >= 29) {
1240
curCell.set("text", "29");
1241
curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1244
curCell.setContent(" ");
1245
curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1249
var curCell = pane.one("#" + paneId + "_7_30");
1250
if (daysInMonth >= 30) {
1251
curCell.set("text", "30");
1252
curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1255
curCell.setContent(" ");
1256
curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1260
var curCell = pane.one("#" + paneId + "_8_31");
1261
if (daysInMonth >= 31) {
1262
curCell.set("text", "31");
1263
curCell.removeClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1266
curCell.setContent(" ");
1267
curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1273
// Update stored pane properties
1274
this._paneProperties[paneId].cutoffCol = cutoffCol;
1275
this._paneProperties[paneId].daysInMonth = daysInMonth;
1276
this._paneProperties[paneId].paneDate = newDate;
1278
// Bring the pane visibility back after all DOM changes are done
1279
pane.setStyle("visibility", "visible");
1284
* A rendering assist method that updates the calendar header based
1285
* on a given date and potentially the provided headerRenderer.
1286
* @method _updateCalendarHeader
1287
* @param {Date} baseDate The date with which to update the calendar header.
1290
_updateCalendarHeader : function (baseDate) {
1291
var headerString = "",
1292
headerRenderer = this.get("headerRenderer");
1294
if (Y.Lang.isString(headerRenderer)) {
1295
headerString = ydate.format(baseDate, {format:headerRenderer});
1297
else if (headerRenderer instanceof Function) {
1298
headerString = headerRenderer.call(this, baseDate);
1301
return headerString;
1305
* A rendering assist method that initializes the calendar header HTML
1306
* based on a given date and potentially the provided headerRenderer.
1307
* @method _updateCalendarHeader
1308
* @param {Date} baseDate The date with which to initialize the calendar header.
1311
_initCalendarHeader : function (baseDate) {
1312
return substitute(substitute(CalendarBase.HEADER_TEMPLATE,
1313
{calheader: this._updateCalendarHeader(baseDate),
1314
calendar_id: this._calendarId}),
1315
CalendarBase.CALENDAR_STRINGS);
1319
* A rendering assist method that initializes the calendar HTML
1320
* based on a given date.
1321
* @method _initCalendarHTML
1322
* @param {Date} baseDate The date with which to initialize the calendar.
1325
_initCalendarHTML : function (baseDate) {
1326
// Instantiate the partials holder
1328
// Counter for iterative template replacement.
1331
// Generate the template for the header
1332
partials["header_template"] = this._initCalendarHeader(baseDate);
1333
partials["calendar_id"] = this._calendarId;
1335
partials["body_template"] = substitute(substitute (CalendarBase.CONTENT_TEMPLATE, partials),
1336
CalendarBase.CALENDAR_STRINGS);
1338
// Instantiate the iterative template replacer function
1339
function paneReplacer () {
1340
var singlePane = this._initCalendarPane(ydate.addMonths(baseDate, counter), partials["calendar_id"]+"_pane_"+counter);
1344
// Go through all occurrences of the calendar_grid_template token and replace it with an appropriate calendar grid.
1345
var output = partials["body_template"].replace(/\{calendar_grid_template\}/g, Y.bind(paneReplacer, this));
1347
// Update the paneNumber count
1348
this._paneNumber = counter;
1355
* The CSS classnames for the calendar templates.
1356
* @property CALENDAR_STRINGS
1363
calendar_grid_class : CAL_GRID,
1364
calendar_body_class : CAL_BODY,
1365
calendar_hd_class : CAL_HD,
1366
calendar_hd_label_class : CAL_HD_LABEL,
1367
calendar_weekdayrow_class : CAL_WDAYROW,
1368
calendar_weekday_class : CAL_WDAY,
1369
calendar_row_class : CAL_ROW,
1370
calendar_day_class : CAL_DAY,
1371
calendar_dayanchor_class : CAL_ANCHOR,
1372
calendar_pane_class : CAL_PANE,
1373
calendar_right_grid_class : CAL_RIGHT_GRID,
1374
calendar_left_grid_class : CAL_LEFT_GRID,
1375
calendar_status_class : CAL_STATUS
1380
ARIA_STATUS_TEMPLATE: '<div role="status" aria-atomic="true" class="{calendar_status_class}"></div>',
1384
updateStatus : function (statusString) {
1386
if (!CalendarBase.AriaStatus) {
1387
CalendarBase.AriaStatus = create(
1388
substitute (CalendarBase.ARIA_STATUS_TEMPLATE,
1389
CalendarBase.CALENDAR_STRINGS));
1390
Y.one("body").append(CalendarBase.AriaStatus);
1393
CalendarBase.AriaStatus.set("text", statusString);
1399
* The main content template for calendar.
1400
* @property CONTENT_TEMPLATE
1405
CONTENT_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
1406
'{header_template}' +
1407
'<div class="yui3-u-1">' +
1408
'{calendar_grid_template}' +
1413
* A single pane template for calendar (same as default CONTENT_TEMPLATE)
1414
* @property ONE_PANE_TEMPLATE
1420
ONE_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
1421
'{header_template}' +
1422
'<div class="yui3-u-1">' +
1423
'{calendar_grid_template}' +
1428
* A two pane template for calendar.
1429
* @property TWO_PANE_TEMPLATE
1435
TWO_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
1436
'{header_template}' +
1437
'<div class="yui3-u-1-2">'+
1438
'<div class = "{calendar_left_grid_class}">' +
1439
'{calendar_grid_template}' +
1442
'<div class="yui3-u-1-2">' +
1443
'<div class = "{calendar_right_grid_class}">' +
1444
'{calendar_grid_template}' +
1449
* A three pane template for calendar.
1450
* @property THREE_PANE_TEMPLATE
1456
THREE_PANE_TEMPLATE: '<div class="yui3-g {calendar_pane_class}" id="{calendar_id}">' +
1457
'{header_template}' +
1458
'<div class="yui3-u-1-3">' +
1459
'<div class = "{calendar_left_grid_class}">' +
1460
'{calendar_grid_template}' +
1463
'<div class="yui3-u-1-3">' +
1464
'{calendar_grid_template}' +
1466
'<div class="yui3-u-1-3">' +
1467
'<div class = "{calendar_right_grid_class}">' +
1468
'{calendar_grid_template}' +
1473
* A template for the calendar grid.
1474
* @property CALENDAR_GRID_TEMPLATE
1479
CALENDAR_GRID_TEMPLATE: '<table class="{calendar_grid_class}" id="{calendar_pane_id}" role="grid" aria-readonly="true" aria-label="{pane_arialabel}" tabindex="{calendar_pane_tabindex}">' +
1481
'{weekday_row_template}' +
1489
* A template for the calendar header.
1490
* @property HEADER_TEMPLATE
1495
HEADER_TEMPLATE: '<div class="yui3-g {calendar_hd_class}">' +
1496
'<div class="yui3-u {calendar_hd_label_class}" id="{calendar_id}_header" aria-role="heading">' +
1502
* A template for the row of weekday names.
1503
* @property WEEKDAY_ROW_TEMPLATE
1508
WEEKDAY_ROW_TEMPLATE: '<tr class="{calendar_weekdayrow_class}" role="row">' +
1513
* A template for a single row of calendar days.
1514
* @property CALDAY_ROW_TEMPLATE
1519
CALDAY_ROW_TEMPLATE: '<tr class="{calendar_row_class}" role="row">' +
1524
* A template for a single cell with a weekday name.
1525
* @property CALDAY_ROW_TEMPLATE
1530
WEEKDAY_TEMPLATE: '<th class="{calendar_weekday_class}" role="columnheader" aria-label="{full_weekdayname}">{weekdayname}</th>',
1533
* A template for a single cell with a calendar day.
1534
* @property CALDAY_TEMPLATE
1539
CALDAY_TEMPLATE: '<td class="{calendar_col_class} {calendar_day_class} {calendar_col_visibility_class}" id="{calendar_day_id}" role="gridcell" tabindex="-1">' +
1544
* The identity of the widget.
1548
* @default 'calendarBase'
1553
NAME: 'calendarBase',
1556
* Static property used to define the default attribute configuration of
1569
* The date corresponding to the current calendar view. Always
1570
* normalized to the first of the month that contains the date
1571
* at assignment time. Used as the first date visible in the
1576
* @default The first of the month containing today's date, as
1577
* set on the end user's system.
1581
setter: function (val) {
1582
var newDate = this._normalizeDate(val);
1583
if (ydate.areEqual(newDate, this.get('date'))) {
1584
return this.get('date');
1593
* A setting specifying whether to shows days from the previous
1594
* month in the visible month's grid, if there are empty preceding
1597
* @attribute showPrevMonth
1606
* A setting specifying whether to shows days from the next
1607
* month in the visible month's grid, if there are empty
1608
* cells available at the end.
1610
* @attribute showNextMonth
1619
* Strings and properties derived from the internationalization packages
1622
* @attribute strings
1627
valueFn: function() { return Y.Intl.get("calendar-base"); }
1631
* Custom header renderer for the calendar.
1633
* @attribute headerRenderer
1634
* @type String | Function
1641
* The name of the rule which all enabled dates should match.
1642
* Either disabledDatesRule or enabledDatesRule should be specified,
1643
* or neither, but not both.
1645
* @attribute enabledDatesRule
1654
* The name of the rule which all disabled dates should match.
1655
* Either disabledDatesRule or enabledDatesRule should be specified,
1656
* or neither, but not both.
1658
* @attribute disabledDatesRule
1662
disabledDatesRule: {
1667
* A read-only attribute providing a list of currently selected dates.
1669
* @attribute selectedDates
1675
getter: function (val) {
1676
return (this._getSelectedDatesList());
1681
* An object of the form {rules:Object, filterFunction:Function},
1682
* providing set of rules and a custom rendering function for
1683
* customizing specific calendar cells.
1685
* @attribute customRenderer
1692
setter: function (val) {
1693
this._rules = val.rules;
1694
this._filterFunction = val.filterFunction;
1702
}, '3.5.1' ,{requires:['widget', 'substitute', 'datatype-date', 'datatype-date-math', 'cssgrids'], lang:['de', 'en', 'fr', 'ja', 'nb-NO', 'pt-BR', 'ru', 'zh-HANT-TW']});