MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / calendar / calendar-debug.js
blobc645ebbcc7f32be329e07edf86652530f486905e
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('calendar', function(Y) {
9 /**
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.
15  *
16  *
17  * @module calendar
18  */
20 var getCN             = Y.ClassNameManager.getClassName,
21     CALENDAR          = 'calendar',
22     KEY_DOWN          = 40,
23     KEY_UP            = 38,
24     KEY_LEFT          = 37,
25     KEY_RIGHT         = 39,
26     KEY_ENTER         = 13,
27     KEY_SPACE         = 32,
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'),
38     os                = Y.UA.os;
40 /** Create a calendar view to represent a single or multiple
41   * month range of dates, rendered as a grid with date and
42   * weekday labels.
43   * 
44   * @class Calendar
45   * @extends CalendarBase
46   * @param config {Object} Configuration object (see Configuration attributes)
47   * @constructor
48   */
49 function Calendar(config) {
50   Calendar.superclass.constructor.apply ( this, arguments );
53 Y.Calendar = Y.extend(Calendar, Y.CalendarBase, {
55     _keyEvents: [],
57     _highlightedDateNode: null,
59   /**
60    * A property tracking the last selected date on the calendar, for the
61    * purposes of multiple selection.
62    *
63    * @property _lastSelectedDate
64    * @type Date
65    * @default null
66    * @private
67    */  
68     _lastSelectedDate: null,
70   /**
71    * Designated initializer. Activates the navigation plugin for the calendar.
72    *
73    * @method initializer
74    */ 
75   initializer : function () {
76     this.plug(Y.Plugin.CalendarNavigator);
79     this._keyEvents = [];
80     this._highlightedDateNode = null;
81     this._lastSelectedDate = null;
82   },
84   /**
85     * syncUI implementation
86     *
87     * Update the scroll position, based on the current value of scrollY
88     * @method syncUI
89     */  
90   syncUI : function () {
92   },
94   /**
95    * Overrides the _bindCalendarEvents placeholder in CalendarBase
96    * and binds calendar events during bindUI stage.
97    * @method _bindCalendarEvents
98    * @protected
99    */   
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);
109   },
111   /**
112    * Highlights a specific date node with keyboard highlight class
113    * @method _highlightDateNode
114    * @param oDate {Date} Date corresponding the node to be highlighted
115    * @protected
116    */   
117   _highlightDateNode : function (oDate) {
118     this._unhighlightCurrentDateNode();
119     var newNode = this._dateToNode(oDate);
120     newNode.focus();
121     newNode.addClass(CAL_DAY_HILITED);
122   },
124   /**
125    * Unhighlights a specific date node currently highlighted with keyboard highlight class
126    * @method _unhighlightCurrentDateNode
127    * @protected
128    */   
129   _unhighlightCurrentDateNode : function () {
130     var allHilitedNodes = this.get("contentBox").all("." + CAL_DAY_HILITED);
131     if (allHilitedNodes) {
132       allHilitedNodes.removeClass(CAL_DAY_HILITED);
133     }
134   },
136   /**
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
140    * @protected
141    */   
142   _getGridNumber : function (gridNode) {
143     var idParts = gridNode.get("id").split("_").reverse();
144         return parseInt(idParts[0], 10);
145   },
147   /**
148    * Handler for loss of focus of calendar grid
149    * @method _blurCalendarGrid
150    * @protected
151    */   
152    _blurCalendarGrid : function (ev) {
153       this._unhighlightCurrentDateNode();
154    },
157   /**
158    * Handler for gain of focus of calendar cell
159    * @method _focusCalendarCell
160    * @protected
161    */ 
162    _focusCalendarCell : function (ev) {
163        this._highlightedDateNode = ev.target;
164        ev.stopPropagation();
165    },
167   /**
168    * Handler for gain of focus of calendar grid
169    * @method _focusCalendarGrid
170    * @protected
171    */ 
172    _focusCalendarGrid : function (ev) {     
173        this._unhighlightCurrentDateNode();
174        this._highlightedDateNode = null;
175    },
177   /**
178    * Handler for keyboard press on a calendar grid
179    * @method _keydownCalendar
180    * @protected
181    */ 
182    _keydownCalendar : function (ev) {
183     var gridNum = this._getGridNumber(ev.target),
184         curDate = !this._highlightedDateNode ? null : this._nodeToDate(this._highlightedDateNode),
185         keyCode = ev.keyCode,
186         dayNum = 0,
187         dir = '';
189         switch(keyCode) {
190           case KEY_DOWN: 
191             dayNum = 7;
192             dir = 's';
193           break;
194           case KEY_UP: 
195             dayNum = -7;
196             dir = 'n';
197           break;
198           case KEY_LEFT: 
199             dayNum = -1;
200             dir = 'w';
201           break;
202           case KEY_RIGHT:
203             dayNum = 1;
204             dir = 'e';
205           break;
206           case KEY_SPACE: case KEY_ENTER:
207             ev.preventDefault();
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);
213             }
214             else if (selMode === "multiple" || selMode === "multiple-sticky") {
215                 if (this._highlightedDateNode.hasClass(CAL_DAY_SELECTED)) {
216                   this._removeDateFromSelection(curDate);
217                 }
218                 else {
219                   this._addDateToSelection(curDate);
220                 }
221              }
222             }
223           break;
224         }
227       if (keyCode == KEY_DOWN || keyCode == KEY_UP || keyCode == KEY_LEFT || keyCode == KEY_RIGHT) {
229       if (!curDate) {
230              curDate = ydate.addMonths(this.get("date"), gridNum);
231              dayNum = 0;
232       }
233               ev.preventDefault();
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));
239           
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);
249                     newPane.focus();
250               }
252               this._highlightDateNode(newDate);
253           }
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);
258             }
259           }
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);
264             }
265           }
267         }
268    },
270   /**
271    * Handles the calendar clicks based on selection mode.
272    * @method _clickCalendar
273    * @param {Event} ev A click event
274    * @private
275    */   
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")) {
283           case("single"):
284                if (clickedCellIsDay) {
285                   if (!clickedCellIsSelected) {
286                     this._clearSelection(true);  
287                     this._addDateToSelection(this._nodeToDate(clickedCell));
288                   }
289              }
290                break;
291             case("multiple-sticky"):
292                if (clickedCellIsDay) {
293                  if (clickedCellIsSelected) {
294                   this._removeDateFromSelection(this._nodeToDate(clickedCell));
295                  }
296                  else {
297                   this._addDateToSelection(this._nodeToDate(clickedCell));
298                  }
299                }
300                break;
301             case("multiple"):
302                if (!ev.metaKey && !ev.ctrlKey && !ev.shiftKey) {
303                     this._clearSelection(true);
304                     this._lastSelectedDate = this._nodeToDate(clickedCell);
305                     this._addDateToSelection(this._lastSelectedDate);
306                }
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;
311                   }
312                   else {
313                     this._lastSelectedDate = this._nodeToDate(clickedCell);
314                     this._addDateToSelection(this._lastSelectedDate);
315                   }
316                }
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;
322                   }
323                   else {
324                     this._lastSelectedDate = this._nodeToDate(clickedCell);
325                     this._addDateToSelection(this._lastSelectedDate);
326                   }
328                }
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;
335                     }
336                     else {
337                       this._clearSelection(true);
338                       this._lastSelectedDate = this._nodeToDate(clickedCell);
339                         this._addDateToSelection(this._lastSelectedDate);
340                     }
341                }
342                break;
343         }
345       if (clickedCellIsDay) {
346    /**
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.
350      *
351      * @event dateClick
352      */
353         this.fire("dateClick", {cell: clickedCell, date: this._nodeToDate(clickedCell)});
354       }
355       else if (clickedCell.hasClass(CAL_PREVMONTH_DAY)) {
356    /**
357      * Fired when any of the previous month's days displayed before the calendar grid
358      * are clicked.
359      *
360      * @event prevMonthClick
361      */
362         this.fire("prevMonthClick");
363       }
364       else if (clickedCell.hasClass(CAL_NEXTMONTH_DAY)) {
365    /**
366      * Fired when any of the next month's days displayed after the calendar grid
367      * are clicked.
368      *
369      * @event nextMonthClick
370      */
371         this.fire("nextMonthClick");
372       }
373     },
375   /**
376    * Subtracts one month from the current calendar view.
377    * @method subtractMonth
378    */   
379   subtractMonth : function (e) {
380     this.set("date", ydate.addMonths(this.get("date"), -1));
381     e.halt();
382   },
384   /**
385    * Subtracts one year from the current calendar view.
386    * @method subtractYear
387    */ 
388   subtractYear : function (e) {
389     this.set("date", ydate.addYears(this.get("date"), -1));
390     e.halt();
391   },
393   /**
394    * Adds one month to the current calendar view.
395    * @method addMonth
396    */   
397   addMonth : function (e) {    
398     this.set("date", ydate.addMonths(this.get("date"), 1));
399     e.halt();
400   },
402   /**
403    * Adds one year to the current calendar view.
404    * @method addYear
405    */   
406   addYear : function (e) {
407     this.set("date", ydate.addYears(this.get("date"), 1));
408     e.halt();
409   }
413    /**
414     * The identity of the widget.
415     *
416     * @property NAME
417     * @type String
418     * @default 'calendar'
419     * @readOnly
420     * @protected
421     * @static
422     */  
423   NAME: "calendar",
425    /**
426     * Static property used to define the default attribute configuration of
427     * the Widget.
428     *
429     * @property ATTRS
430     * @type {Object}
431     * @protected
432     * @static
433     */  
434   ATTRS: {
436     /**
437      * A setting specifying the type of selection the calendar allows.
438      * Possible values include:
439      * <ul>
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>
445      *
446      * @attribute selectionMode
447      * @type String
448      * @default single
449      */
450     selectionMode: {
451       value: "single"
452     },
454     /**
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
458      * calendar.
459      *
460      * @attribute date
461      * @type Date
462      * @default Today's date as set on the user's computer.
463      */
464     date: {
465       value: new Date(),
466       lazyAdd: false,
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))) {
475                 return newDate;
476             }
478             else if (minDate && ydate.isGreater(minDate, newDate)) {
479                    return minDate;
480             }
482             else if (maxDate && ydate.isGreater(newTopDate, maxDate)) {
483                 var actualMaxDate = ydate.addMonths(maxDate, -1*(this._paneNumber - 1));
484                   return actualMaxDate;
485             }
486      }
487     },
489     /**
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.
493      *
494      * @attribute minimumDate
495      * @type Date
496      * @default null
497      */
498     minimumDate: {
499       value: null,
500       setter: function (val) {
501         if (val) {
502           var curDate = this.get('date'),
503               newMinDate = this._normalizeDate(val);
504           if (curDate && !ydate.isGreaterOrEqual(curDate, newMinDate)) {
505               this.set('date', newMinDate);
506           }
507           return newMinDate;
508         }
509         else {
510           return this._normalizeDate(val);
511         }
512       }
513     },
515     /**
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.
519      *
520      * @attribute maximumDate
521      * @type Date
522      * @default null
523      */
524     maximumDate: {
525       value: null,
526       setter: function (val) {
527         if (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)));
532           }
533           return newMaxDate;
534         }
535         else {
536           return val;
537         }
538       }
539     }
540   }
544 }, '3.5.1' ,{requires:['calendar-base', 'calendarnavigator'], lang:['de', 'en', 'fr', 'ja', 'nb-NO', 'pt-BR', 'ru', 'zh-HANT-TW']});