2
YUI 3.10.3 (build 2fb5187)
3
Copyright 2013 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
8
YUI.add('calendar', function (Y, NAME) {
11
* The Calendar component is a UI widget that allows users
12
* to view dates in a two-dimensional month grid, as well as
13
* to select one or more dates, or ranges of dates. Calendar
14
* is generated dynamically and relies on the developer to
15
* provide for a progressive enhancement alternative.
21
var getCN = Y.ClassNameManager.getClassName,
22
CALENDAR = 'calendar',
29
CAL_DAY_SELECTED = getCN(CALENDAR, 'day-selected'),
30
CAL_DAY_HILITED = getCN(CALENDAR, 'day-highlighted'),
31
CAL_DAY = getCN(CALENDAR, 'day'),
32
CAL_PREVMONTH_DAY = getCN(CALENDAR, 'prevmonth-day'),
33
CAL_NEXTMONTH_DAY = getCN(CALENDAR, 'nextmonth-day'),
34
CAL_GRID = getCN(CALENDAR, 'grid'),
35
ydate = Y.DataType.Date,
36
CAL_PANE = getCN(CALENDAR, 'pane'),
39
/** Create a calendar view to represent a single or multiple
40
* month range of dates, rendered as a grid with date and
44
* @extends CalendarBase
45
* @param config {Object} Configuration object (see Configuration attributes)
49
Calendar.superclass.constructor.apply ( this, arguments );
52
Y.Calendar = Y.extend(Calendar, Y.CalendarBase, {
56
_highlightedDateNode: null,
59
* A property tracking the last selected date on the calendar, for the
60
* purposes of multiple selection.
62
* @property _lastSelectedDate
67
_lastSelectedDate: null,
70
* Designated initializer. Activates the navigation plugin for the calendar.
74
initializer : function () {
75
this.plug(Y.Plugin.CalendarNavigator);
78
this._highlightedDateNode = null;
79
this._lastSelectedDate = null;
83
* Overrides the _bindCalendarEvents placeholder in CalendarBase
84
* and binds calendar events during bindUI stage.
85
* @method _bindCalendarEvents
88
_bindCalendarEvents : function () {
89
var contentBox = this.get('contentBox'),
90
pane = contentBox.one("." + CAL_PANE);
92
pane.on("selectstart", this._preventSelectionStart);
93
pane.delegate("click", this._clickCalendar, "." + CAL_DAY + ", ." + CAL_PREVMONTH_DAY + ", ." + CAL_NEXTMONTH_DAY, this);
94
pane.delegate("keydown", this._keydownCalendar, "." + CAL_GRID, this);
95
pane.delegate("focus", this._focusCalendarGrid, "." + CAL_GRID, this);
96
pane.delegate("focus", this._focusCalendarCell, "." + CAL_DAY, this);
97
pane.delegate("blur", this._blurCalendarGrid, "." + CAL_GRID + ",." + CAL_DAY, this);
101
* Prevents text selection if it is started within the calendar pane
102
* @method _preventSelectionStart
103
* @param event {Event} The selectstart event
106
_preventSelectionStart : function (event) {
107
event.preventDefault();
111
* Highlights a specific date node with keyboard highlight class
112
* @method _highlightDateNode
113
* @param oDate {Date} Date corresponding the node to be highlighted
116
_highlightDateNode : function (oDate) {
117
this._unhighlightCurrentDateNode();
118
var newNode = this._dateToNode(oDate);
120
newNode.addClass(CAL_DAY_HILITED);
124
* Unhighlights a specific date node currently highlighted with keyboard highlight class
125
* @method _unhighlightCurrentDateNode
128
_unhighlightCurrentDateNode : function () {
129
var allHilitedNodes = this.get("contentBox").all("." + CAL_DAY_HILITED);
130
if (allHilitedNodes) {
131
allHilitedNodes.removeClass(CAL_DAY_HILITED);
136
* Returns the grid number for a specific calendar grid (for multi-grid templates)
137
* @method _getGridNumber
138
* @param gridNode {Node} Node corresponding to a specific grid
141
_getGridNumber : function (gridNode) {
142
var idParts = gridNode.get("id").split("_").reverse();
144
return parseInt(idParts[0], 10);
148
* Handler for loss of focus of calendar grid
149
* @method _blurCalendarGrid
152
_blurCalendarGrid : function () {
153
this._unhighlightCurrentDateNode();
158
* Handler for gain of focus of calendar cell
159
* @method _focusCalendarCell
162
_focusCalendarCell : function (ev) {
163
this._highlightedDateNode = ev.target;
164
ev.stopPropagation();
168
* Handler for gain of focus of calendar grid
169
* @method _focusCalendarGrid
172
_focusCalendarGrid : function () {
173
this._unhighlightCurrentDateNode();
174
this._highlightedDateNode = null;
178
* Handler for keyboard press on a calendar grid
179
* @method _keydownCalendar
182
_keydownCalendar : function (ev) {
183
var gridNum = this._getGridNumber(ev.target),
184
curDate = !this._highlightedDateNode ? null : this._nodeToDate(this._highlightedDateNode),
185
keyCode = ev.keyCode,
211
case KEY_SPACE: case KEY_ENTER:
213
if (this._highlightedDateNode) {
214
selMode = this.get("selectionMode");
215
if (selMode === "single" && !this._highlightedDateNode.hasClass(CAL_DAY_SELECTED)) {
216
this._clearSelection(true);
217
this._addDateToSelection(curDate);
218
} else if (selMode === "multiple" || selMode === "multiple-sticky") {
219
if (this._highlightedDateNode.hasClass(CAL_DAY_SELECTED)) {
220
this._removeDateFromSelection(curDate);
222
this._addDateToSelection(curDate);
230
if (keyCode === KEY_DOWN || keyCode === KEY_UP || keyCode === KEY_LEFT || keyCode === KEY_RIGHT) {
233
curDate = ydate.addMonths(this.get("date"), gridNum);
239
newDate = ydate.addDays(curDate, dayNum);
240
startDate = this.get("date");
241
endDate = ydate.addMonths(this.get("date"), this._paneNumber - 1);
242
lastPaneDate = new Date(endDate);
243
endDate.setDate(ydate.daysInMonth(endDate));
245
if (ydate.isInRange(newDate, startDate, endDate)) {
247
var paneShift = (newDate.getMonth() - curDate.getMonth()) % 10;
249
if (paneShift != 0) {
250
var newGridNum = gridNum + paneShift,
251
contentBox = this.get('contentBox'),
252
newPane = contentBox.one("#" + this._calendarId + "_pane_" + newGridNum);
256
this._highlightDateNode(newDate);
257
} else if (ydate.isGreater(startDate, newDate)) {
258
if (!ydate.isGreaterOrEqual(this.get("minimumDate"), startDate)) {
259
this.set("date", ydate.addMonths(startDate, -1));
260
this._highlightDateNode(newDate);
262
} else if (ydate.isGreater(newDate, endDate)) {
263
if (!ydate.isGreaterOrEqual(lastPaneDate, this.get("maximumDate"))) {
264
this.set("date", ydate.addMonths(startDate, 1));
265
this._highlightDateNode(newDate);
272
* Handles the calendar clicks based on selection mode.
273
* @method _clickCalendar
274
* @param {Event} ev A click event
277
_clickCalendar : function (ev) {
278
var clickedCell = ev.currentTarget,
279
clickedCellIsDay = clickedCell.hasClass(CAL_DAY) &&
280
!clickedCell.hasClass(CAL_PREVMONTH_DAY) &&
281
!clickedCell.hasClass(CAL_NEXTMONTH_DAY),
283
clickedCellIsSelected = clickedCell.hasClass(CAL_DAY_SELECTED),
286
switch (this.get("selectionMode")) {
288
if (clickedCellIsDay) {
289
if (!clickedCellIsSelected) {
290
this._clearSelection(true);
291
this._addDateToSelection(this._nodeToDate(clickedCell));
295
case("multiple-sticky"):
296
if (clickedCellIsDay) {
297
if (clickedCellIsSelected) {
298
this._removeDateFromSelection(this._nodeToDate(clickedCell));
300
this._addDateToSelection(this._nodeToDate(clickedCell));
305
if (clickedCellIsDay) {
306
if (!ev.metaKey && !ev.ctrlKey && !ev.shiftKey) {
307
this._clearSelection(true);
308
this._lastSelectedDate = this._nodeToDate(clickedCell);
309
this._addDateToSelection(this._lastSelectedDate);
310
} else if (((os === 'macintosh' && ev.metaKey) || (os !== 'macintosh' && ev.ctrlKey)) && !ev.shiftKey) {
311
if (clickedCellIsSelected) {
312
this._removeDateFromSelection(this._nodeToDate(clickedCell));
313
this._lastSelectedDate = null;
315
this._lastSelectedDate = this._nodeToDate(clickedCell);
316
this._addDateToSelection(this._lastSelectedDate);
318
} else if (((os === 'macintosh' && ev.metaKey) || (os !== 'macintosh' && ev.ctrlKey)) && ev.shiftKey) {
319
if (this._lastSelectedDate) {
320
selectedDate = this._nodeToDate(clickedCell);
321
this._addDateRangeToSelection(selectedDate, this._lastSelectedDate);
322
this._lastSelectedDate = selectedDate;
324
this._lastSelectedDate = this._nodeToDate(clickedCell);
325
this._addDateToSelection(this._lastSelectedDate);
327
} else if (ev.shiftKey) {
328
if (this._lastSelectedDate) {
329
selectedDate = this._nodeToDate(clickedCell);
330
this._clearSelection(true);
331
this._addDateRangeToSelection(selectedDate, this._lastSelectedDate);
332
this._lastSelectedDate = selectedDate;
334
this._clearSelection(true);
335
this._lastSelectedDate = this._nodeToDate(clickedCell);
336
this._addDateToSelection(this._lastSelectedDate);
343
if (clickedCellIsDay) {
345
* Fired when a specific date cell in the calendar is clicked. The event carries a
346
* payload which includes a `cell` property corresponding to the node of the actual
347
* date cell, and a `date` property, with the `Date` that was clicked.
351
this.fire("dateClick", {cell: clickedCell, date: this._nodeToDate(clickedCell)});
352
} else if (clickedCell.hasClass(CAL_PREVMONTH_DAY)) {
354
* Fired when any of the previous month's days displayed before the calendar grid
357
* @event prevMonthClick
359
this.fire("prevMonthClick");
360
} else if (clickedCell.hasClass(CAL_NEXTMONTH_DAY)) {
362
* Fired when any of the next month's days displayed after the calendar grid
365
* @event nextMonthClick
367
this.fire("nextMonthClick");
372
* Subtracts one month from the current calendar view.
373
* @method subtractMonth
374
* @return {Calendar} A reference to this object
377
subtractMonth : function (e) {
378
this.set("date", ydate.addMonths(this.get("date"), -1));
386
* Subtracts one year from the current calendar view.
387
* @method subtractYear
388
* @return {Calendar} A reference to this object
391
subtractYear : function (e) {
392
this.set("date", ydate.addYears(this.get("date"), -1));
400
* Adds one month to the current calendar view.
402
* @return {Calendar} A reference to this object
405
addMonth : function (e) {
406
this.set("date", ydate.addMonths(this.get("date"), 1));
414
* Adds one year to the current calendar view.
416
* @return {Calendar} A reference to this object
419
addYear : function (e) {
420
this.set("date", ydate.addYears(this.get("date"), 1));
428
* The identity of the widget.
432
* @default 'calendar'
440
* Static property used to define the default attribute configuration of
451
* A setting specifying the type of selection the calendar allows.
452
* Possible values include:
454
* <li>`single` - One date at a time</li>
455
* <li>`multiple-sticky` - Multiple dates, selected one at a time (the dates "stick"). This option
456
* is appropriate for mobile devices, where function keys from the keyboard are not available.</li>
457
* <li>`multiple` - Multiple dates, selected with Ctrl/Meta keys for additional single
458
* dates, and Shift key for date ranges.</li>
460
* @attribute selectionMode
469
* The date corresponding to the current calendar view. Always
470
* normalized to the first of the month that contains the date
471
* at assignment time. Used as the first date visible in the
476
* @default Today's date as set on the user's computer.
481
setter: function (val) {
483
var newDate = this._normalizeDate(val),
484
newTopDate = ydate.addMonths(newDate, this._paneNumber - 1),
485
minDate = this.get("minimumDate"),
486
maxDate = this.get("maximumDate"),
489
if ((!minDate || ydate.isGreaterOrEqual(newDate, minDate)) &&
490
(!maxDate || ydate.isGreaterOrEqual(maxDate, newTopDate))
493
} else if (minDate && ydate.isGreater(minDate, newDate)) {
495
} else if (maxDate && ydate.isGreater(newTopDate, maxDate)) {
496
actualMaxDate = ydate.addMonths(maxDate, -1*(this._paneNumber - 1));
497
return actualMaxDate;
503
* The minimum date that can be displayed by the calendar. The calendar will not
504
* allow dates earlier than this one to be set, and will reset any earlier date to
505
* this date. Should be `null` if no minimum date is needed.
507
* @attribute minimumDate
513
setter: function (val) {
515
var curDate = this.get('date'),
516
newMinDate = this._normalizeDate(val);
517
if (curDate && !ydate.isGreaterOrEqual(curDate, newMinDate)) {
518
this.set('date', newMinDate);
522
return this._normalizeDate(val);
528
* The maximum date that can be displayed by the calendar. The calendar will not
529
* allow dates later than this one to be set, and will reset any later date to
530
* this date. Should be `null` if no maximum date is needed.
532
* @attribute maximumDate
538
setter: function (val) {
540
var curDate = this.get('date'),
541
newMaxDate = this._normalizeDate(val);
542
if (curDate && !ydate.isGreaterOrEqual(val, ydate.addMonths(curDate, this._paneNumber - 1))) {
543
this.set('date', ydate.addMonths(newMaxDate, -1*(this._paneNumber -1)));