3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('calendar', function(Y) {
10 * The Calendar component is a UI widget that allows users
11 * to view dates in a two-dimensional month grid, as well as
12 * to select one or more dates, or ranges of dates. Calendar
13 * is generated dynamically and relies on the developer to
14 * provide for a progressive enhancement alternative.
20 var getCN = Y.ClassNameManager.getClassName,
21 CALENDAR = 'calendar',
28 CAL_HD = getCN(CALENDAR, 'header'),
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 delegate = Y.delegate,
37 CAL_PANE = getCN(CALENDAR, 'pane'),
40 /** Create a calendar view to represent a single or multiple
41 * month range of dates, rendered as a grid with date and
45 * @extends CalendarBase
46 * @param config {Object} Configuration object (see Configuration attributes)
49 function Calendar(config) {
50 Calendar.superclass.constructor.apply ( this, arguments );
53 Y.Calendar = Y.extend(Calendar, Y.CalendarBase, {
57 _highlightedDateNode: null,
60 * A property tracking the last selected date on the calendar, for the
61 * purposes of multiple selection.
63 * @property _lastSelectedDate
68 _lastSelectedDate: null,
71 * Designated initializer. Activates the navigation plugin for the calendar.
75 initializer : function () {
76 this.plug(Y.Plugin.CalendarNavigator);
80 this._highlightedDateNode = null;
81 this._lastSelectedDate = null;
85 * syncUI implementation
87 * Update the scroll position, based on the current value of scrollY
90 syncUI : function () {
95 * Overrides the _bindCalendarEvents placeholder in CalendarBase
96 * and binds calendar events during bindUI stage.
97 * @method _bindCalendarEvents
100 _bindCalendarEvents : function () {
101 var contentBox = this.get('contentBox'),
102 pane = contentBox.one("." + CAL_PANE);
103 pane.on("selectstart", function (ev) { ev.preventDefault();});
104 pane.delegate("click", this._clickCalendar, "." + CAL_DAY, this);
105 pane.delegate("keydown", this._keydownCalendar, "." + CAL_GRID, this);
106 pane.delegate("focus", this._focusCalendarGrid, "." + CAL_GRID, this);
107 pane.delegate("focus", this._focusCalendarCell, "." + CAL_DAY, this);
108 pane.delegate("blur", this._blurCalendarGrid, "." + CAL_GRID + ",." + CAL_DAY, this);
112 * Highlights a specific date node with keyboard highlight class
113 * @method _highlightDateNode
114 * @param oDate {Date} Date corresponding the node to be highlighted
117 _highlightDateNode : function (oDate) {
118 this._unhighlightCurrentDateNode();
119 var newNode = this._dateToNode(oDate);
121 newNode.addClass(CAL_DAY_HILITED);
125 * Unhighlights a specific date node currently highlighted with keyboard highlight class
126 * @method _unhighlightCurrentDateNode
129 _unhighlightCurrentDateNode : function () {
130 var allHilitedNodes = this.get("contentBox").all("." + CAL_DAY_HILITED);
131 if (allHilitedNodes) {
132 allHilitedNodes.removeClass(CAL_DAY_HILITED);
137 * Returns the grid number for a specific calendar grid (for multi-grid templates)
138 * @method _getGridNumber
139 * @param gridNode {Node} Node corresponding to a specific grid
142 _getGridNumber : function (gridNode) {
143 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 (ev) {
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 (ev) {
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,
206 case KEY_SPACE: case KEY_ENTER:
208 if (this._highlightedDateNode) {
209 var selMode = this.get("selectionMode");
210 if (selMode === "single" && !this._highlightedDateNode.hasClass(CAL_DAY_SELECTED)) {
211 this._clearSelection(true);
212 this._addDateToSelection(curDate);
214 else if (selMode === "multiple" || selMode === "multiple-sticky") {
215 if (this._highlightedDateNode.hasClass(CAL_DAY_SELECTED)) {
216 this._removeDateFromSelection(curDate);
219 this._addDateToSelection(curDate);
227 if (keyCode == KEY_DOWN || keyCode == KEY_UP || keyCode == KEY_LEFT || keyCode == KEY_RIGHT) {
230 curDate = ydate.addMonths(this.get("date"), gridNum);
234 var newDate = ydate.addDays(curDate, dayNum),
235 startDate = this.get("date"),
236 endDate = ydate.addMonths(this.get("date"), this._paneNumber - 1),
237 lastPaneDate = new Date(endDate);
238 endDate.setDate(ydate.daysInMonth(endDate));
240 if (ydate.isInRange(newDate, startDate, endDate)) {
242 var paneShift = (newDate.getMonth() - curDate.getMonth()) % 10;
245 if (paneShift != 0) {
246 var newGridNum = gridNum + paneShift,
247 contentBox = this.get('contentBox'),
248 newPane = contentBox.one("#" + this._calendarId + "_pane_" + newGridNum);
252 this._highlightDateNode(newDate);
254 else if (ydate.isGreater(startDate, newDate)) {
255 if (!ydate.isGreaterOrEqual(this.get("minimumDate"), startDate)) {
256 this.set("date", ydate.addMonths(startDate, -1));
257 this._highlightDateNode(newDate);
260 else if (ydate.isGreater(newDate, endDate)) {
261 if (!ydate.isGreaterOrEqual(lastPaneDate, this.get("maximumDate"))) {
262 this.set("date", ydate.addMonths(startDate, 1));
263 this._highlightDateNode(newDate);
271 * Handles the calendar clicks based on selection mode.
272 * @method _clickCalendar
273 * @param {Event} ev A click event
276 _clickCalendar : function (ev) {
277 var clickedCell = ev.target,
278 clickedCellIsDay = clickedCell.hasClass(CAL_DAY) &&
279 !clickedCell.hasClass(CAL_PREVMONTH_DAY) &&
280 !clickedCell.hasClass(CAL_NEXTMONTH_DAY),
281 clickedCellIsSelected = clickedCell.hasClass(CAL_DAY_SELECTED);
282 switch (this.get("selectionMode")) {
284 if (clickedCellIsDay) {
285 if (!clickedCellIsSelected) {
286 this._clearSelection(true);
287 this._addDateToSelection(this._nodeToDate(clickedCell));
291 case("multiple-sticky"):
292 if (clickedCellIsDay) {
293 if (clickedCellIsSelected) {
294 this._removeDateFromSelection(this._nodeToDate(clickedCell));
297 this._addDateToSelection(this._nodeToDate(clickedCell));
302 if (!ev.metaKey && !ev.ctrlKey && !ev.shiftKey) {
303 this._clearSelection(true);
304 this._lastSelectedDate = this._nodeToDate(clickedCell);
305 this._addDateToSelection(this._lastSelectedDate);
307 else if (((os == 'macintosh' && ev.metaKey) || (os != 'macintosh' && ev.ctrlKey)) && !ev.shiftKey) {
308 if (clickedCellIsSelected) {
309 this._removeDateFromSelection(this._nodeToDate(clickedCell));
310 this._lastSelectedDate = null;
313 this._lastSelectedDate = this._nodeToDate(clickedCell);
314 this._addDateToSelection(this._lastSelectedDate);
317 else if (((os == 'macintosh' && ev.metaKey) || (os != 'macintosh' && ev.ctrlKey)) && ev.shiftKey) {
318 if (this._lastSelectedDate) {
319 var selectedDate = this._nodeToDate(clickedCell);
320 this._addDateRangeToSelection(selectedDate, this._lastSelectedDate);
321 this._lastSelectedDate = selectedDate;
324 this._lastSelectedDate = this._nodeToDate(clickedCell);
325 this._addDateToSelection(this._lastSelectedDate);
329 else if (ev.shiftKey) {
330 if (this._lastSelectedDate) {
331 var selectedDate = this._nodeToDate(clickedCell);
332 this._clearSelection(true);
333 this._addDateRangeToSelection(selectedDate, this._lastSelectedDate);
334 this._lastSelectedDate = selectedDate;
337 this._clearSelection(true);
338 this._lastSelectedDate = this._nodeToDate(clickedCell);
339 this._addDateToSelection(this._lastSelectedDate);
345 if (clickedCellIsDay) {
347 * Fired when a specific date cell in the calendar is clicked. The event carries a
348 * payload which includes a `cell` property corresponding to the node of the actual
349 * date cell, and a `date` property, with the `Date` that was clicked.
353 this.fire("dateClick", {cell: clickedCell, date: this._nodeToDate(clickedCell)});
355 else if (clickedCell.hasClass(CAL_PREVMONTH_DAY)) {
357 * Fired when any of the previous month's days displayed before the calendar grid
360 * @event prevMonthClick
362 this.fire("prevMonthClick");
364 else if (clickedCell.hasClass(CAL_NEXTMONTH_DAY)) {
366 * Fired when any of the next month's days displayed after the calendar grid
369 * @event nextMonthClick
371 this.fire("nextMonthClick");
376 * Subtracts one month from the current calendar view.
377 * @method subtractMonth
379 subtractMonth : function (e) {
380 this.set("date", ydate.addMonths(this.get("date"), -1));
385 * Subtracts one year from the current calendar view.
386 * @method subtractYear
388 subtractYear : function (e) {
389 this.set("date", ydate.addYears(this.get("date"), -1));
394 * Adds one month to the current calendar view.
397 addMonth : function (e) {
398 this.set("date", ydate.addMonths(this.get("date"), 1));
403 * Adds one year to the current calendar view.
406 addYear : function (e) {
407 this.set("date", ydate.addYears(this.get("date"), 1));
414 * The identity of the widget.
418 * @default 'calendar'
426 * Static property used to define the default attribute configuration of
437 * A setting specifying the type of selection the calendar allows.
438 * Possible values include:
440 * <li>`single` - One date at a time</li>
441 * <li>`multiple-sticky` - Multiple dates, selected one at a time (the dates "stick"). This option
442 * is appropriate for mobile devices, where function keys from the keyboard are not available.</li>
443 * <li>`multiple` - Multiple dates, selected with Ctrl/Meta keys for additional single
444 * dates, and Shift key for date ranges.</li>
446 * @attribute selectionMode
455 * The date corresponding to the current calendar view. Always
456 * normalized to the first of the month that contains the date
457 * at assignment time. Used as the first date visible in the
462 * @default Today's date as set on the user's computer.
467 setter: function (val) {
469 var newDate = this._normalizeDate(val),
470 newTopDate = ydate.addMonths(newDate, this._paneNumber - 1);
471 var minDate = this.get("minimumDate");
472 var maxDate = this.get("maximumDate");
473 if ((!minDate || ydate.isGreaterOrEqual(newDate, minDate)) &&
474 (!maxDate || ydate.isGreaterOrEqual(maxDate, newTopDate))) {
478 else if (minDate && ydate.isGreater(minDate, newDate)) {
482 else if (maxDate && ydate.isGreater(newTopDate, maxDate)) {
483 var actualMaxDate = ydate.addMonths(maxDate, -1*(this._paneNumber - 1));
484 return actualMaxDate;
490 * The minimum date that can be displayed by the calendar. The calendar will not
491 * allow dates earlier than this one to be set, and will reset any earlier date to
492 * this date. Should be `null` if no minimum date is needed.
494 * @attribute minimumDate
500 setter: function (val) {
502 var curDate = this.get('date'),
503 newMinDate = this._normalizeDate(val);
504 if (curDate && !ydate.isGreaterOrEqual(curDate, newMinDate)) {
505 this.set('date', newMinDate);
510 return this._normalizeDate(val);
516 * The maximum date that can be displayed by the calendar. The calendar will not
517 * allow dates later than this one to be set, and will reset any later date to
518 * this date. Should be `null` if no maximum date is needed.
520 * @attribute maximumDate
526 setter: function (val) {
528 var curDate = this.get('date'),
529 newMaxDate = this._normalizeDate(val);
530 if (curDate && !ydate.isGreaterOrEqual(val, ydate.addMonths(curDate, this._paneNumber - 1))) {
531 this.set('date', ydate.addMonths(newMaxDate, -1*(this._paneNumber -1)));
544 }, '3.5.1' ,{requires:['calendar-base', 'calendarnavigator'], lang:['de', 'en', 'fr', 'ja', 'nb-NO', 'pt-BR', 'ru', 'zh-HANT-TW']});