~smagoun/whoopsie/whoopsie-lp1017637

« back to all changes in this revision

Viewing changes to backend/stats/static/js/yui/build/calendar-base/calendar-base-debug.js

  • Committer: Evan Dandrea
  • Date: 2012-05-09 05:53:45 UTC
  • Revision ID: evan.dandrea@canonical.com-20120509055345-z2j41tmcbf4as5uf
The backend now lives in lp:daisy and the website (errors.ubuntu.com) now lives in lp:errors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.5.0 (build 5089)
3
 
Copyright 2012 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('calendar-base', function(Y) {
8
 
 
9
 
/**
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. 
14
 
 * @module calendar
15
 
 * @submodule calendar-base
16
 
 */
17
 
    
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'),
38
 
    L           = Y.Lang,
39
 
    node        = Y.Node,
40
 
    create      = node.create,
41
 
    substitute  = Y.substitute,
42
 
    each        = Y.each,
43
 
    hasVal      = Y.Array.hasValue,
44
 
    iOf         = Y.Array.indexOf,
45
 
    hasKey      = Y.Object.hasKey,
46
 
    setVal      = Y.Object.setValue,
47
 
    owns        = Y.Object.owns,
48
 
    isEmpty     = Y.Object.isEmpty,
49
 
    ydate       = Y.DataType.Date;
50
 
 
51
 
/** Create a calendar view to represent a single or multiple
52
 
  * month range of dates, rendered as a grid with date and
53
 
  * weekday labels.
54
 
  * 
55
 
  * @class CalendarBase
56
 
  * @extends Widget
57
 
  * @param config {Object} Configuration object (see Configuration 
58
 
  * attributes)
59
 
  * @constructor
60
 
  */
61
 
function CalendarBase(config) {
62
 
  CalendarBase.superclass.constructor.apply ( this, arguments );
63
 
}
64
 
 
65
 
 
66
 
 
67
 
Y.CalendarBase = Y.extend( CalendarBase, Y.Widget, {
68
 
 
69
 
  /**
70
 
   * A storage for various properties of individual month
71
 
   * panes.
72
 
   *
73
 
   * @property _paneProperties
74
 
   * @type Object
75
 
   * @private
76
 
   */
77
 
  _paneProperties : {},
78
 
 
79
 
  /**
80
 
   * The number of month panes in the calendar, deduced
81
 
   * from the CONTENT_TEMPLATE's number of {calendar_grid}
82
 
   * tokens.
83
 
   *
84
 
   * @property _paneNumber
85
 
   * @type Number
86
 
   * @private
87
 
   */
88
 
  _paneNumber : 1,
89
 
 
90
 
  /**
91
 
   * The unique id used to prefix various elements of this
92
 
   * calendar instance.
93
 
   *
94
 
   * @property _calendarId
95
 
   * @type String
96
 
   * @private
97
 
   */
98
 
  _calendarId : null,
99
 
 
100
 
  /**
101
 
   * The hash map of selected dates, populated with
102
 
   * selectDates() and deselectDates() methods 
103
 
   *
104
 
   * @property _selectedDates
105
 
   * @type Object
106
 
   * @private
107
 
   */
108
 
  _selectedDates : {},
109
 
 
110
 
  /**
111
 
   * A private copy of the rules object, populated
112
 
   * by setting the customRenderer attribute.
113
 
   *
114
 
   * @property _rules
115
 
   * @type Object
116
 
   * @private
117
 
   */
118
 
  _rules : {},
119
 
 
120
 
  /**
121
 
   * A private copy of the filterFunction, populated
122
 
   * by setting the customRenderer attribute.
123
 
   *
124
 
   * @property _filterFunction
125
 
   * @type Function
126
 
   * @private
127
 
   */
128
 
  _filterFunction : null,
129
 
 
130
 
  /**
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. 
135
 
   *
136
 
   * @property _storedDateCells
137
 
   * @type Object
138
 
   * @private
139
 
   */
140
 
  _storedDateCells : {},
141
 
 
142
 
  /**
143
 
   * Designated initializer
144
 
   * Initializes instance-level properties of
145
 
   * calendar.
146
 
   *
147
 
   * @method initializer
148
 
   */  
149
 
  initializer : function () {
150
 
    this._paneProperties = {};
151
 
    this._calendarId = Y.guid('calendar');
152
 
    this._selectedDates = {};
153
 
    this._rules = {};
154
 
    this._storedDateCells = {};
155
 
  },
156
 
 
157
 
  /**
158
 
   * renderUI implementation
159
 
   *
160
 
   * Creates a visual representation of the calendar based on existing parameters. 
161
 
   * @method renderUI
162
 
   */  
163
 
  renderUI : function () {
164
 
 
165
 
      var contentBox = this.get('contentBox');
166
 
      contentBox.appendChild(this._initCalendarHTML(this.get('date')));
167
 
        if (this.get('showPrevMonth')) {
168
 
            this._afterShowPrevMonthChange();
169
 
        }
170
 
        if (this.get('showNextMonth')) {
171
 
            this._afterShowNextMonthChange();
172
 
        }
173
 
      this._renderCustomRules();
174
 
      this._renderSelectedDates();
175
 
 
176
 
    this.get("boundingBox").setAttribute("aria-labelledby", this._calendarId + "_header");
177
 
 
178
 
  },
179
 
  /**
180
 
   * bindUI implementation
181
 
   *
182
 
   * Assigns listeners to relevant events that change the state
183
 
   * of the calendar.
184
 
   * @method bindUI
185
 
   */ 
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();
196
 
  },
197
 
 
198
 
  /**
199
 
    * syncUI implementation
200
 
    *
201
 
    * Update the scroll position, based on the current value of scrollY
202
 
    * @method syncUI
203
 
    */  
204
 
  syncUI : function () {
205
 
      if (this.get('showPrevMonth')) {
206
 
           this._afterShowPrevMonthChange();
207
 
 
208
 
      }
209
 
 
210
 
      if (this.get('showNextMonth')) {
211
 
           this._afterShowNextMonthChange();
212
 
      }
213
 
  },
214
 
 
215
 
    /**
216
 
     * An internal utility method that generates a list of selected dates 
217
 
     * from the hash storage.
218
 
     *
219
 
     * @method _getSelectedDatesList
220
 
     * @protected
221
 
     * @return {Array} The array of `Date`s that are currently selected.
222
 
     */
223
 
    _getSelectedDatesList : function () {
224
 
      var output = [];
225
 
      each (this._selectedDates, function (year) {
226
 
        each (year, function (month) {
227
 
          each (month, function (day) {
228
 
           output.push (day);
229
 
           }, this);
230
 
        }, this);
231
 
      }, this);
232
 
      return output;
233
 
    },
234
 
 
235
 
    /**
236
 
     * A utility method that returns all dates selected in a specific month.
237
 
     *
238
 
     * @method _getSelectedDatesInMonth
239
 
     * @param {Date} oDate corresponding to the month for which selected dates
240
 
     * are requested.
241
 
     * @protected
242
 
     * @return {Array} The array of `Date`s in a given month that are currently selected.
243
 
     */
244
 
    _getSelectedDatesInMonth : function (oDate) {
245
 
      var year = oDate.getFullYear(),
246
 
          month = oDate.getMonth();
247
 
      
248
 
        if (hasKey(this._selectedDates, year) && hasKey(this._selectedDates[year], month)) {
249
 
           return Y.Object.values(this._selectedDates[year][month]); 
250
 
        }
251
 
        else {
252
 
           return [];
253
 
        }
254
 
      },
255
 
 
256
 
    /**
257
 
     * An internal rendering method that modifies a date cell to have the
258
 
     * selected CSS class if the date cell is visible.
259
 
     *
260
 
     * @method _renderSelectedDate
261
 
     * @param {Date} oDate The date corresponding to a specific date cell.
262
 
     * @private
263
 
     */
264
 
    _renderSelectedDate : function (oDate) {
265
 
        if (this._isDateVisible(oDate)) {
266
 
            this._dateToNode(oDate).addClass(CAL_DAY_SELECTED).setAttribute("aria-selected", true);
267
 
        }
268
 
    },
269
 
 
270
 
    /**
271
 
     * An internal rendering method that modifies a date cell to remove the
272
 
     * selected CSS class if the date cell is visible.
273
 
     *
274
 
     * @method _renderUnelectedDate
275
 
     * @param {Date} oDate The date corresponding to a specific date cell.
276
 
     * @private
277
 
     */
278
 
    _renderUnselectedDate : function (oDate) {
279
 
        if (this._isDateVisible(oDate)) {
280
 
            this._dateToNode(oDate).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
281
 
        }
282
 
    },
283
 
 
284
 
    /**
285
 
     * An internal utility method that checks whether a particular date
286
 
     * is in the current view of the calendar.
287
 
     *
288
 
     * @method _isDateVisible
289
 
     * @param {Date} oDate The date corresponding to a specific date cell.
290
 
     * @private
291
 
     * @return {boolean} Returns true if the given date is in the current 
292
 
     * view of the calendar.
293
 
     */
294
 
    _isDateVisible : function (oDate) {
295
 
      var minDate = this.get("date"),
296
 
          maxDate = ydate.addMonths(minDate, this._paneNumber - 1),
297
 
          oDateTime = this._normalizeDate(oDate).getTime();
298
 
          
299
 
      if (minDate.getTime() <= oDateTime && oDateTime <= maxDate) {
300
 
          return true;
301
 
      }
302
 
      else {
303
 
          return false;
304
 
      }
305
 
    },
306
 
 
307
 
    /**
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.
312
 
     *
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.
316
 
     * @private
317
 
     * @return {boolean} Returns true if the given number is in the given list.
318
 
     */
319
 
    _isNumInList : function (num, strList) {
320
 
        if (strList == "all") {
321
 
            return true;
322
 
        }
323
 
        else {
324
 
            var elements = strList.split(","),
325
 
                i = elements.length;
326
 
 
327
 
            while (i--) {
328
 
                var range = elements[i].split("-");
329
 
                if (range.length == 2 && num >= parseInt(range[0], 10) && num <= parseInt(range[1], 10)) {
330
 
                    return true;
331
 
                }
332
 
                else if (range.length == 1 && (parseInt(elements[i], 10) == num)) {
333
 
                    return true;
334
 
                }
335
 
            }
336
 
            return false;   
337
 
        }
338
 
    },
339
 
 
340
 
    /**
341
 
     * Given a specific date, returns an array of rules (from the customRenderer rule set)
342
 
     * that the given date matches.
343
 
     *
344
 
     * @method _getRulesForDate
345
 
     * @param {Date} oDate The date for which an array of rules is needed
346
 
     * @private
347
 
     * @return {Array} Returns an array of `String`s, each containg the name of
348
 
     * a rule that the given date matches.
349
 
     */
350
 
    _getRulesForDate : function (oDate) {
351
 
      var year = oDate.getFullYear(),
352
 
          month = oDate.getMonth(),
353
 
          date = oDate.getDate(),
354
 
          wday = oDate.getDay(),
355
 
          rules = this._rules, 
356
 
          outputRules = [],
357
 
          years, months, dates, days;
358
 
 
359
 
      for (years in rules) {
360
 
          if (this._isNumInList(year, years)) {
361
 
              if (L.isString(rules[years])) {
362
 
                  outputRules.push(rules[years]);
363
 
              }
364
 
              else {
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]);
369
 
                          }
370
 
                          else {
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]);
375
 
                                      }
376
 
                                      else {
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]);
381
 
                                                  }
382
 
                                              }
383
 
                                          }
384
 
                                      }
385
 
                                  }
386
 
                              }
387
 
                          }
388
 
                      }
389
 
                  } 
390
 
              }
391
 
          }
392
 
      }
393
 
      return outputRules;
394
 
    },
395
 
 
396
 
    /**
397
 
     * A utility method which, given a specific date and a name of the rule,
398
 
     * checks whether the date matches the given rule.
399
 
     *
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.
403
 
     * @private
404
 
     * @return {boolean} Returns true if the date matches the given rule.
405
 
     *
406
 
     */
407
 
    _matchesRule : function (oDate, rule) {
408
 
        return (iOf(this._getRulesForDate(oDate), rule) >= 0);
409
 
    },
410
 
 
411
 
    /**
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
416
 
     * @private
417
 
     * @return {boolean} Returns true if the date can be selected; false otherwise.
418
 
     */
419
 
    _canBeSelected : function (oDate) {
420
 
       
421
 
       var enabledDatesRule = this.get("enabledDatesRule"),
422
 
           disabledDatesRule = this.get("disabledDatesRule");
423
 
 
424
 
       if (enabledDatesRule) {
425
 
           return this._matchesRule(oDate, enabledDatesRule);
426
 
       }
427
 
       else if (disabledDatesRule) {
428
 
           return !this._matchesRule(oDate, disabledDatesRule);
429
 
       }
430
 
       else {
431
 
           return true;
432
 
       }
433
 
    },
434
 
 
435
 
    /**
436
 
     * Selects a given date or array of dates.
437
 
     * @method selectDates
438
 
     * @param {Date|Array} dates A `Date` or `Array` of `Date`s.
439
 
     */
440
 
    selectDates : function (dates) {
441
 
      if (ydate.isValidDate(dates)) {
442
 
         this._addDateToSelection(dates);
443
 
      }
444
 
      else if (L.isArray(dates)) {
445
 
         this._addDatesToSelection(dates);
446
 
      }
447
 
    },
448
 
 
449
 
    /**
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.
455
 
     */
456
 
    deselectDates : function (dates) {
457
 
      if (!dates) {
458
 
         this._clearSelection();
459
 
      }
460
 
      else if (ydate.isValidDate(dates)) {
461
 
         this._removeDateFromSelection(dates);
462
 
      }
463
 
      else if (L.isArray(dates)) {
464
 
         this._removeDatesFromSelection(dates);
465
 
      }
466
 
    },
467
 
 
468
 
    /**
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
474
 
     * date selections.
475
 
     * @private
476
 
     */
477
 
    _addDateToSelection : function (oDate, index) {
478
 
 
479
 
      if (this._canBeSelected(oDate)) {
480
 
 
481
 
        var year = oDate.getFullYear(),
482
 
            month = oDate.getMonth(),
483
 
            day = oDate.getDate();
484
 
        
485
 
        if (hasKey(this._selectedDates, year)) {
486
 
            if (hasKey(this._selectedDates[year], month)) {
487
 
                this._selectedDates[year][month][day] = oDate;
488
 
            }
489
 
            else {
490
 
                this._selectedDates[year][month] = {};
491
 
                this._selectedDates[year][month][day] = oDate;
492
 
            }
493
 
        }
494
 
        else {
495
 
            this._selectedDates[year] = {};
496
 
            this._selectedDates[year][month] = {};
497
 
            this._selectedDates[year][month][day] = oDate;
498
 
        }
499
 
 
500
 
        this._selectedDates = setVal(this._selectedDates, [year, month, day], oDate);
501
 
        this._renderSelectedDate(oDate);
502
 
 
503
 
        if (!index) {
504
 
        this._fireSelectionChange();
505
 
        }
506
 
      }
507
 
    },
508
 
 
509
 
    /**
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.
513
 
     * @private
514
 
     */
515
 
    _addDatesToSelection : function (datesArray) {
516
 
        each(datesArray, this._addDateToSelection, this);
517
 
        this._fireSelectionChange();
518
 
    },
519
 
 
520
 
    /**
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.
525
 
     * @private
526
 
     */
527
 
    _addDateRangeToSelection : function (startDate, endDate) {
528
 
        var timezoneDifference = (endDate.getTimezoneOffset() - startDate.getTimezoneOffset())*60000,
529
 
            startTime = startDate.getTime(),
530
 
            endTime   = endDate.getTime();
531
 
            
532
 
            if (startTime > endTime) {
533
 
                var tempTime = startTime;
534
 
                startTime = endTime;
535
 
                endTime = tempTime + timezoneDifference;
536
 
            }
537
 
            else {
538
 
                endTime = endTime - timezoneDifference;
539
 
            }
540
 
 
541
 
 
542
 
        for (var time = startTime; time <= endTime; time += 86400000) {
543
 
            var addedDate = new Date(time);
544
 
                addedDate.setHours(12);
545
 
            this._addDateToSelection(addedDate, time);
546
 
        }
547
 
        this._fireSelectionChange();
548
 
    },
549
 
 
550
 
    /**
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
556
 
     * date selections.
557
 
     * @private
558
 
     */
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);
568
 
               if (!index) {
569
 
                 this._fireSelectionChange();
570
 
               }
571
 
        }
572
 
    },
573
 
 
574
 
    /**
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.
578
 
     * @private
579
 
     */
580
 
    _removeDatesFromSelection : function (datesArray) {
581
 
        each(datesArray, this._removeDateDromSelection);
582
 
        this._fireSelectionChange();
583
 
    },
584
 
 
585
 
    /**
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.
590
 
     * @private
591
 
     */
592
 
    _removeDateRangeFromSelection : function (startDate, endDate) {
593
 
        var startTime = startDate.getTime(),
594
 
            endTime   = endDate.getTime();
595
 
        
596
 
        for (var time = startTime; time <= endTime; time += 86400000) {
597
 
            this._removeDateFromSelection(new Date(time), time);
598
 
        }
599
 
 
600
 
        this._fireSelectionChange();    
601
 
    },
602
 
 
603
 
    /**
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.
608
 
     * @private
609
 
     */
610
 
    _clearSelection : function (noevent) {
611
 
        this._selectedDates = {};
612
 
        this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
613
 
        if (!noevent) {
614
 
          this._fireSelectionChange();
615
 
        }
616
 
    },
617
 
 
618
 
    /**
619
 
     * A utility method that fires a selectionChange event.
620
 
     * @method _fireSelectionChange
621
 
     * @private
622
 
     */
623
 
    _fireSelectionChange : function () {
624
 
 
625
 
   /**
626
 
     * Fired when the set of selected dates changes. Contains a payload with
627
 
     * a `newSelection` property with an array of selected dates.
628
 
     *
629
 
     * @event selectionChange
630
 
     */
631
 
      this.fire("selectionChange", {newSelection: this._getSelectedDatesList()});
632
 
    },
633
 
 
634
 
    /**
635
 
     * A utility method that restores cells modified by custom formatting.
636
 
     * @method _restoreModifiedCells
637
 
     * @private
638
 
     */
639
 
    _restoreModifiedCells : function () {
640
 
      var contentbox = this.get("contentBox"),
641
 
          id;
642
 
      for (id in this._storedDateCells) {
643
 
          contentbox.one("#" + id).replace(this._storedDateCells[id]);
644
 
          delete this._storedDateCells[id];
645
 
      }
646
 
    },
647
 
 
648
 
    /**
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
652
 
     * @private
653
 
     */
654
 
    _renderCustomRules : function () {
655
 
 
656
 
        this.get("contentBox").all("." + CAL_DAY + ",." + CAL_NEXTMONTH_DAY).removeClass(SELECTION_DISABLED).setAttribute("aria-disabled", false);
657
 
        
658
 
        if (!isEmpty(this._rules)) {
659
 
        var enRule = this.get("enabledDatesRule"),
660
 
            disRule = this.get("disabledDatesRule");
661
 
 
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);
671
 
                        }
672
 
                        
673
 
                    if (L.isFunction(this._filterFunction)) {
674
 
                        this._storedDateCells[dateNode.get("id")] = dateNode.cloneNode(true);
675
 
                        this._filterFunction (date, dateNode, matchingRules);
676
 
                    }
677
 
                }
678
 
                else if (enRule) {
679
 
                   var dateNode = this._dateToNode(date);
680
 
                   dateNode.addClass(SELECTION_DISABLED).setAttribute("aria-disabled", true);
681
 
                }
682
 
                },
683
 
             this);
684
 
          }
685
 
       }         
686
 
    },
687
 
 
688
 
    /**
689
 
     * A rendering assist method that renders all cells that are currently selected.
690
 
     * @method _renderSelectedDates
691
 
     * @private
692
 
     */
693
 
  _renderSelectedDates : function () {
694
 
    this.get("contentBox").all("." + CAL_DAY_SELECTED).removeClass(CAL_DAY_SELECTED).setAttribute("aria-selected", false);
695
 
    
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);
701
 
                        },
702
 
             this);
703
 
      }
704
 
  },
705
 
 
706
 
    /**
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
711
 
     * @protected
712
 
     * @return {Node} The node wrapping the DOM element of the cell the date 
713
 
     * corresponds to.
714
 
     */
715
 
  _dateToNode : function (oDate) {
716
 
    var day = oDate.getDate(),
717
 
            col = 0,
718
 
            daymod = day%7,
719
 
            paneNum = (12 + oDate.getMonth() - this.get("date").getMonth()) % 12,
720
 
            paneId = this._calendarId + "_pane_" + paneNum,
721
 
            cutoffCol = this._paneProperties[paneId].cutoffCol;
722
 
 
723
 
        switch (daymod) {
724
 
          case (0):
725
 
             if (cutoffCol >= 6) {
726
 
               col = 12;
727
 
             }
728
 
             else {
729
 
               col = 5;
730
 
             }
731
 
             break;
732
 
          case (1):
733
 
               col = 6;
734
 
             break;
735
 
          case (2):
736
 
             if (cutoffCol > 0) {
737
 
               col = 7;
738
 
             }
739
 
             else {
740
 
               col = 0;
741
 
             }
742
 
             break;
743
 
          case (3):
744
 
             if (cutoffCol > 1) {
745
 
               col = 8;
746
 
             }
747
 
             else {
748
 
               col = 1;
749
 
             }
750
 
             break;
751
 
          case (4):
752
 
             if (cutoffCol > 2) {
753
 
               col = 9;
754
 
             }
755
 
             else {
756
 
               col = 2;
757
 
             }
758
 
             break;
759
 
          case (5):
760
 
             if (cutoffCol > 3) {
761
 
               col = 10;
762
 
             }
763
 
             else {
764
 
               col = 3;
765
 
             }
766
 
             break;
767
 
          case (6):
768
 
             if (cutoffCol > 4) {
769
 
               col = 11;
770
 
             }
771
 
             else {
772
 
               col = 4;
773
 
             }
774
 
             break;
775
 
        }
776
 
        return(this.get("contentBox").one("#" + this._calendarId + "_pane_" + paneNum + "_" + col + "_" + day));  
777
 
 
778
 
  },
779
 
 
780
 
    /**
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.
785
 
     * @protected
786
 
     * @return {Date} The date corresponding to the DOM element that the given node wraps.
787
 
     */
788
 
  _nodeToDate : function (oNode) {
789
 
    
790
 
        var idParts = oNode.get("id").split("_").reverse(),
791
 
            paneNum = parseInt(idParts[2], 10),
792
 
            day  = parseInt(idParts[0], 10);
793
 
 
794
 
        var shiftedDate = ydate.addMonths(this.get("date"), paneNum),
795
 
            year = shiftedDate.getFullYear(),
796
 
            month = shiftedDate.getMonth();
797
 
 
798
 
    return new Date(year, month, day, 12, 0, 0, 0);
799
 
  },
800
 
 
801
 
    /**
802
 
     * A placeholder method, called from bindUI, to bind the Calendar events.
803
 
     * @method _bindCalendarEvents
804
 
     * @protected
805
 
     */
806
 
  _bindCalendarEvents : function () {
807
 
    
808
 
  },
809
 
 
810
 
    /**
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
815
 
     * @protected
816
 
     * @return {Date} The normalized date, set to the first of the month, with time
817
 
     * set to noon.
818
 
     */
819
 
    _normalizeDate : function (date) {
820
 
      if (date) {
821
 
       return new Date(date.getFullYear(), date.getMonth(), 1, 12, 0, 0, 0);
822
 
      }
823
 
      else {
824
 
       return null;
825
 
      }  
826
 
    },
827
 
 
828
 
 
829
 
    /**
830
 
     * A render assist utility method that computes the cutoff column for the calendar 
831
 
     * rendering mask.
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)
835
 
     * @private
836
 
     * @return {Number} The number of the cutoff column.
837
 
     */
838
 
    _getCutoffColumn : function (date, firstday) {
839
 
 
840
 
   var distance = this._normalizeDate(date).getDay() - firstday;
841
 
   var cutOffColumn = 6 - (distance + 7)%7;
842
 
   return cutOffColumn;
843
 
 
844
 
    },
845
 
 
846
 
    /**
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
851
 
     * modified.
852
 
     * @protected
853
 
     */
854
 
    _turnPrevMonthOn : function (pane) {
855
 
        
856
 
        var pane_id = pane.get("id"),
857
 
            pane_date = this._paneProperties[pane_id].paneDate,
858
 
            daysInPrevMonth = ydate.daysInMonth(ydate.addMonths(pane_date, -1));
859
 
 
860
 
        if (!this._paneProperties[pane_id].hasOwnProperty("daysInPrevMonth")) {
861
 
          this._paneProperties[pane_id].daysInPrevMonth = 0;
862
 
        }
863
 
 
864
 
        if (daysInPrevMonth != this._paneProperties[pane_id].daysInPrevMonth) {
865
 
        
866
 
        this._paneProperties[pane_id].daysInPrevMonth = daysInPrevMonth;
867
 
 
868
 
        for (var cell = 5; cell >= 0; cell--) 
869
 
           {
870
 
            pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).set('text', daysInPrevMonth--);
871
 
           }
872
 
 
873
 
        }
874
 
 
875
 
 
876
 
    },
877
 
 
878
 
    /**
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
883
 
     * modified.
884
 
     * @protected
885
 
     */
886
 
    _turnPrevMonthOff : function (pane) {
887
 
          var pane_id = pane.get("id");
888
 
        this._paneProperties[pane_id].daysInPrevMonth = 0;
889
 
 
890
 
        for (var cell = 5; cell >= 0; cell--) 
891
 
           {
892
 
            pane.one("#" + pane_id + "_" + cell + "_" + (cell-5)).setContent("&nbsp;");
893
 
           }      
894
 
    },
895
 
 
896
 
    /**
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.
901
 
     * @private
902
 
     */
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);     
910
 
    },
911
 
 
912
 
    /**
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
917
 
     * modified.
918
 
     * @protected
919
 
     */
920
 
    _turnNextMonthOn : function (pane) {       
921
 
          var dayCounter = 1,
922
 
              pane_id = pane.get("id"),
923
 
              daysInMonth = this._paneProperties[pane_id].daysInMonth,
924
 
              cutoffCol = this._paneProperties[pane_id].cutoffCol;
925
 
 
926
 
        for (var cell = daysInMonth - 22; cell < cutoffCol + 7; cell++) 
927
 
           {
928
 
            pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);
929
 
           }
930
 
 
931
 
        var startingCell = cutoffCol;
932
 
        if (daysInMonth == 31 && (cutoffCol <= 1)) {
933
 
          startingCell = 2;
934
 
        }
935
 
        else if (daysInMonth == 30 && cutoffCol === 0) {
936
 
          startingCell = 1;
937
 
        }
938
 
  
939
 
        for (var cell = startingCell ; cell < cutoffCol + 7; cell++) {
940
 
            pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).set("text", dayCounter++).addClass(CAL_NEXTMONTH_DAY);    
941
 
        }
942
 
    },
943
 
 
944
 
    /**
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
949
 
     * modified.
950
 
     * @protected
951
 
     */
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;
956
 
 
957
 
        for (var cell = daysInMonth - 22; cell <= 12; cell++) 
958
 
           {
959
 
            pane.one("#" + pane_id + "_" + cell + "_" + (cell+23)).setContent("&nbsp;").addClass(CAL_NEXTMONTH_DAY);
960
 
           }
961
 
 
962
 
        var startingCell = 0;
963
 
        if (daysInMonth == 31 && (cutoffCol <= 1)) {
964
 
          startingCell = 2;
965
 
        }
966
 
        else if (daysInMonth == 30 && cutoffCol === 0) {
967
 
          startingCell = 1;
968
 
        }
969
 
  
970
 
        for (var cell = startingCell ; cell <= 12; cell++) {
971
 
            pane.one("#" + pane_id + "_" + cell + "_" + (cell+30)).setContent("&nbsp;").addClass(CAL_NEXTMONTH_DAY);   
972
 
        }   
973
 
    },
974
 
 
975
 
    /**
976
 
     * The handler for the change in the showNextMonth attribute.
977
 
     * @method _afterShowNextMonthChange
978
 
     * @private
979
 
     */
980
 
    _afterShowNextMonthChange : function () {
981
 
      
982
 
      var contentBox = this.get('contentBox'),
983
 
          lastPane = contentBox.one("#" + this._calendarId + "_pane_" + (this._paneNumber - 1));
984
 
          this._cleanUpNextMonthCells(lastPane);  
985
 
 
986
 
 
987
 
      if (this.get('showNextMonth')) {
988
 
          this._turnNextMonthOn(lastPane);
989
 
        }
990
 
 
991
 
        else {
992
 
          this._turnNextMonthOff(lastPane);
993
 
        }
994
 
 
995
 
    },
996
 
 
997
 
    /**
998
 
     * The handler for the change in the showPrevMonth attribute.
999
 
     * @method _afterShowPrevMonthChange
1000
 
     * @private
1001
 
     */
1002
 
    _afterShowPrevMonthChange : function () {
1003
 
      var contentBox = this.get('contentBox'),
1004
 
          firstPane = contentBox.one("#" + this._calendarId + "_pane_" + 0);
1005
 
 
1006
 
      if (this.get('showPrevMonth')) {
1007
 
         this._turnPrevMonthOn(firstPane);
1008
 
      }
1009
 
 
1010
 
      else {
1011
 
         this._turnPrevMonthOff(firstPane);
1012
 
      }
1013
 
      
1014
 
    },
1015
 
 
1016
 
     /**
1017
 
     * The handler for the change in the headerRenderer attribute.
1018
 
     * @method _afterHeaderRendererChange
1019
 
     * @private
1020
 
     */ 
1021
 
  _afterHeaderRendererChange : function () {
1022
 
    var headerCell = this.get("contentBox").one("." + CAL_HD_LABEL);
1023
 
    headerCell.setContent(this._updateCalendarHeader(this.get('date')));
1024
 
  },
1025
 
 
1026
 
     /**
1027
 
     * The handler for the change in the customRenderer attribute.
1028
 
     * @method _afterCustomRendererChange
1029
 
     * @private
1030
 
     */ 
1031
 
    _afterCustomRendererChange : function () {
1032
 
        this._restoreModifiedCells();
1033
 
        this._renderCustomRules();
1034
 
    },
1035
 
 
1036
 
     /**
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
1041
 
     * @private
1042
 
     */ 
1043
 
  _afterDateChange : function () {
1044
 
    
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"),
1049
 
        counter = 0;
1050
 
 
1051
 
  contentBox.setStyle("visibility", "hidden");
1052
 
  headerCell.setContent(this._updateCalendarHeader(currentDate));
1053
 
  
1054
 
    this._restoreModifiedCells();
1055
 
 
1056
 
    calendarPanes.each(function (curNode) {
1057
 
                      this._rerenderCalendarPane(ydate.addMonths(currentDate, counter++), 
1058
 
                                            curNode);
1059
 
                                        }, this);
1060
 
 
1061
 
     this._afterShowPrevMonthChange();
1062
 
     this._afterShowNextMonthChange();
1063
 
 
1064
 
    this._renderCustomRules();
1065
 
    this._renderSelectedDates();
1066
 
      
1067
 
  contentBox.setStyle("visibility", "visible");
1068
 
  },
1069
 
 
1070
 
 
1071
 
     /**
1072
 
     * A rendering assist method that initializes the HTML for a single
1073
 
     * calendar pane.
1074
 
     * @method _initCalendarPane
1075
 
     * @param {Date} baseDate The date corresponding to the month of the given
1076
 
     * calendar pane.
1077
 
     * @param {String} pane_id The id of the pane, to be used as a prefix for
1078
 
     * element ids in the given pane.
1079
 
     * @private
1080
 
     */ 
1081
 
  _initCalendarPane : function (baseDate, pane_id) {
1082
 
        // Initialize final output HTML string
1083
 
    var calString = '',
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
1096
 
        partials = {};
1097
 
        
1098
 
            // Initialize the partial template for the weekday row cells.
1099
 
        partials["weekday_row"] = '';
1100
 
      
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]});
1107
 
      }
1108
 
        
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);
1111
 
 
1112
 
      // Populate the array of individual row HTML strings
1113
 
          for (var row = 0; row <= 5; row++) {
1114
 
          
1115
 
              for (var column = 0; column <= 12; column++) {  
1116
 
             
1117
 
             // Compute the value of the date that needs to populate the cell
1118
 
             var date = 7*row - 5 + column;
1119
 
 
1120
 
             // Compose the value of the unique id of the current calendar cell
1121
 
             var id_date = pane_id + "_" + column + "_" + date;
1122
 
 
1123
 
             // Set the calendar day class to one of three possible values
1124
 
             var calendar_day_class = CAL_DAY;
1125
 
 
1126
 
             if (date < 1) {
1127
 
              calendar_day_class = CAL_PREVMONTH_DAY;
1128
 
             }
1129
 
                 else if (date > daysInMonth) {
1130
 
                  calendar_day_class = CAL_NEXTMONTH_DAY;
1131
 
                 }
1132
 
 
1133
 
                 // Cut off dates that fall before the first and after the last date of the month
1134
 
             if (date < 1 || date > daysInMonth) {
1135
 
               date = "&nbsp;";
1136
 
             }
1137
 
             
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;
1140
 
 
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, 
1143
 
                                         {day_content: date,
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});
1148
 
             }
1149
 
            }
1150
 
      
1151
 
      // Instantiate the partial calendar pane body template
1152
 
      partials["body_template"] = '';
1153
 
      
1154
 
      // Populate the body template with the rows templates
1155
 
      each (row_array, function (v) {
1156
 
         partials["body_template"] += substitute(CalendarBase.CALDAY_ROW_TEMPLATE, 
1157
 
                                                       {calday_row: v});
1158
 
      });
1159
 
 
1160
 
      // Populate the calendar grid id
1161
 
      partials["calendar_pane_id"] = pane_id;
1162
 
 
1163
 
      // Populate the calendar pane tabindex
1164
 
      partials["calendar_pane_tabindex"] = this.get("tabIndex");
1165
 
      partials["pane_arialabel"] = ydate.format(baseDate, {format:"%B %Y"});
1166
 
 
1167
 
 
1168
 
      // Generate final output by substituting class names.
1169
 
          var output = substitute(substitute (CalendarBase.CALENDAR_GRID_TEMPLATE, partials),
1170
 
                              CalendarBase.CALENDAR_STRINGS);
1171
 
 
1172
 
        // Store the initialized pane information
1173
 
 
1174
 
        this._paneProperties[pane_id] = {cutoffCol: cutoffCol, daysInMonth: daysInMonth, paneDate: baseDate};
1175
 
 
1176
 
      return output;
1177
 
  },
1178
 
 
1179
 
     /**
1180
 
     * A rendering assist method that rerenders a specified calendar pane, based
1181
 
     * on a new Date. 
1182
 
     * @method _rerenderCalendarPane
1183
 
     * @param {Date} newDate The date corresponding to the month of the given
1184
 
     * calendar pane.
1185
 
     * @param {Node} pane The node corresponding to the calendar pane to be rerenders.
1186
 
     * @private
1187
 
     */ 
1188
 
  _rerenderCalendarPane : function (newDate, pane) {
1189
 
 
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");
1198
 
  
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"}));
1202
 
  
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);
1207
 
      
1208
 
        if (column < cutoffCol || column >= (cutoffCol + 7)) {
1209
 
            currentColumn.addClass(CAL_COL_HIDDEN);
1210
 
        }
1211
 
        else {
1212
 
          // Clean up dates in visible columns to account for the correct number of days in a month
1213
 
          switch(column)
1214
 
          {
1215
 
         case 0:
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);
1220
 
          }
1221
 
          else {
1222
 
            curCell.setContent("&nbsp;");
1223
 
            curCell.addClass(CAL_NEXTMONTH_DAY).addClass(CAL_DAY);
1224
 
          }
1225
 
          break;
1226
 
         case 1:
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);
1231
 
          }
1232
 
          else {
1233
 
            curCell.setContent("&nbsp;");
1234
 
            curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1235
 
          }
1236
 
          break;
1237
 
         case 6:
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);
1242
 
          }
1243
 
          else {
1244
 
            curCell.setContent("&nbsp;");
1245
 
            curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1246
 
          }
1247
 
          break;
1248
 
         case 7:
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);
1253
 
          }
1254
 
          else {
1255
 
            curCell.setContent("&nbsp;");
1256
 
            curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1257
 
          }
1258
 
          break;
1259
 
         case 8:
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);
1264
 
          }
1265
 
          else {
1266
 
            curCell.setContent("&nbsp;");
1267
 
            curCell.removeClass(CAL_DAY).addClass(CAL_NEXTMONTH_DAY);
1268
 
          }
1269
 
          break;
1270
 
          } 
1271
 
        }
1272
 
        }
1273
 
    // Update stored pane properties
1274
 
    this._paneProperties[paneId].cutoffCol = cutoffCol;
1275
 
    this._paneProperties[paneId].daysInMonth = daysInMonth;
1276
 
    this._paneProperties[paneId].paneDate = newDate;
1277
 
  
1278
 
  // Bring the pane visibility back after all DOM changes are done    
1279
 
  pane.setStyle("visibility", "visible");
1280
 
 
1281
 
  },
1282
 
 
1283
 
     /**
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.
1288
 
     * @private
1289
 
     */ 
1290
 
    _updateCalendarHeader : function (baseDate) {
1291
 
      var headerString = "",
1292
 
      headerRenderer = this.get("headerRenderer");
1293
 
      
1294
 
      if (Y.Lang.isString(headerRenderer)) {
1295
 
        headerString = ydate.format(baseDate, {format:headerRenderer});
1296
 
      }
1297
 
      else if (headerRenderer instanceof Function) {
1298
 
        headerString = headerRenderer.call(this, baseDate);
1299
 
      }
1300
 
      
1301
 
      return headerString;  
1302
 
    },
1303
 
 
1304
 
     /**
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.
1309
 
     * @private
1310
 
     */ 
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);
1316
 
    },
1317
 
 
1318
 
     /**
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.
1323
 
     * @private
1324
 
     */          
1325
 
  _initCalendarHTML : function (baseDate) {
1326
 
        // Instantiate the partials holder
1327
 
        var partials = {},
1328
 
            // Counter for iterative template replacement.
1329
 
            counter = 0;
1330
 
        
1331
 
        // Generate the template for the header   
1332
 
        partials["header_template"] =  this._initCalendarHeader(baseDate);
1333
 
        partials["calendar_id"] = this._calendarId;
1334
 
 
1335
 
          partials["body_template"] = substitute(substitute (CalendarBase.CONTENT_TEMPLATE, partials),
1336
 
                                             CalendarBase.CALENDAR_STRINGS);
1337
 
 
1338
 
        // Instantiate the iterative template replacer function        
1339
 
        function paneReplacer () {
1340
 
          var singlePane = this._initCalendarPane(ydate.addMonths(baseDate, counter), partials["calendar_id"]+"_pane_"+counter);
1341
 
          counter++;
1342
 
          return singlePane;
1343
 
        }
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));
1346
 
 
1347
 
        // Update the paneNumber count
1348
 
        this._paneNumber = counter;
1349
 
 
1350
 
    return output;
1351
 
  }
1352
 
}, {
1353
 
  
1354
 
   /**
1355
 
    * The CSS classnames for the calendar templates.
1356
 
    * @property CALENDAR_STRINGS
1357
 
    * @type Object
1358
 
    * @readOnly
1359
 
    * @protected
1360
 
    * @static
1361
 
    */  
1362
 
  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
1376
 
  },
1377
 
 
1378
 
  /*
1379
 
 
1380
 
  ARIA_STATUS_TEMPLATE: '<div role="status" aria-atomic="true" class="{calendar_status_class}"></div>',
1381
 
 
1382
 
  AriaStatus : null,
1383
 
 
1384
 
  updateStatus : function (statusString) {
1385
 
 
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);
1391
 
    }
1392
 
 
1393
 
      CalendarBase.AriaStatus.set("text", statusString);
1394
 
  },
1395
 
 
1396
 
  */
1397
 
 
1398
 
   /**
1399
 
    * The main content template for calendar.
1400
 
    * @property CONTENT_TEMPLATE
1401
 
    * @type String
1402
 
    * @protected
1403
 
    * @static
1404
 
    */  
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}' +
1409
 
                            '</div>' +
1410
 
                 '</div>',
1411
 
 
1412
 
   /**
1413
 
    * A single pane template for calendar (same as default CONTENT_TEMPLATE)
1414
 
    * @property ONE_PANE_TEMPLATE
1415
 
    * @type String
1416
 
    * @protected
1417
 
    * @readOnly
1418
 
    * @static
1419
 
    */  
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}' +
1424
 
                            '</div>' +
1425
 
                 '</div>',
1426
 
 
1427
 
   /**
1428
 
    * A two pane template for calendar.
1429
 
    * @property TWO_PANE_TEMPLATE
1430
 
    * @type String
1431
 
    * @protected
1432
 
    * @readOnly
1433
 
    * @static
1434
 
    */  
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}' +
1440
 
                                    '</div>' +
1441
 
                            '</div>' +
1442
 
                            '<div class="yui3-u-1-2">' +
1443
 
                                    '<div class = "{calendar_right_grid_class}">' +
1444
 
                                 '{calendar_grid_template}' +
1445
 
                                    '</div>' +
1446
 
                            '</div>' +                   
1447
 
                 '</div>',
1448
 
   /**
1449
 
    * A three pane template for calendar.
1450
 
    * @property THREE_PANE_TEMPLATE
1451
 
    * @type String
1452
 
    * @protected
1453
 
    * @readOnly
1454
 
    * @static
1455
 
    */  
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}' +
1461
 
                                    '</div>' + 
1462
 
                            '</div>' +
1463
 
                            '<div class="yui3-u-1-3">' +
1464
 
                                 '{calendar_grid_template}' +
1465
 
                            '</div>' +      
1466
 
                            '<div class="yui3-u-1-3">' +
1467
 
                                    '<div class = "{calendar_right_grid_class}">' +
1468
 
                                 '{calendar_grid_template}' +
1469
 
                                    '</div>' + 
1470
 
                            '</div>' +                                             
1471
 
                 '</div>',
1472
 
   /**
1473
 
    * A template for the calendar grid.
1474
 
    * @property CALENDAR_GRID_TEMPLATE
1475
 
    * @type String
1476
 
    * @protected
1477
 
    * @static
1478
 
    */    
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}">' + 
1480
 
                           '<thead>' +
1481
 
                        '{weekday_row_template}' +
1482
 
                           '</thead>' +
1483
 
                           '<tbody>' + 
1484
 
                            '{body_template}' +
1485
 
                           '</tbody>' +
1486
 
                          '</table>',
1487
 
 
1488
 
   /**
1489
 
    * A template for the calendar header.
1490
 
    * @property HEADER_TEMPLATE
1491
 
    * @type String
1492
 
    * @protected
1493
 
    * @static
1494
 
    */   
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">' + 
1497
 
                              '{calheader}' +
1498
 
                         '</div>' +
1499
 
                   '</div>',
1500
 
 
1501
 
   /**
1502
 
    * A template for the row of weekday names.
1503
 
    * @property WEEKDAY_ROW_TEMPLATE
1504
 
    * @type String
1505
 
    * @protected
1506
 
    * @static
1507
 
    */ 
1508
 
  WEEKDAY_ROW_TEMPLATE: '<tr class="{calendar_weekdayrow_class}" role="row">' + 
1509
 
                           '{weekday_row}' +
1510
 
              '</tr>',
1511
 
 
1512
 
   /**
1513
 
    * A template for a single row of calendar days.
1514
 
    * @property CALDAY_ROW_TEMPLATE
1515
 
    * @type String
1516
 
    * @protected
1517
 
    * @static
1518
 
    */ 
1519
 
  CALDAY_ROW_TEMPLATE: '<tr class="{calendar_row_class}" role="row">' + 
1520
 
                 '{calday_row}' + 
1521
 
              '</tr>',
1522
 
 
1523
 
   /**
1524
 
    * A template for a single cell with a weekday name.
1525
 
    * @property CALDAY_ROW_TEMPLATE
1526
 
    * @type String
1527
 
    * @protected
1528
 
    * @static
1529
 
    */ 
1530
 
  WEEKDAY_TEMPLATE: '<th class="{calendar_weekday_class}" role="columnheader" aria-label="{full_weekdayname}">{weekdayname}</th>',
1531
 
 
1532
 
   /**
1533
 
    * A template for a single cell with a calendar day.
1534
 
    * @property CALDAY_TEMPLATE
1535
 
    * @type String
1536
 
    * @protected
1537
 
    * @static
1538
 
    */ 
1539
 
  CALDAY_TEMPLATE: '<td class="{calendar_col_class} {calendar_day_class} {calendar_col_visibility_class}" id="{calendar_day_id}" role="gridcell" tabindex="-1">' +
1540
 
                       '{day_content}' + 
1541
 
                   '</td>',
1542
 
 
1543
 
   /**
1544
 
    * The identity of the widget.
1545
 
    *
1546
 
    * @property NAME
1547
 
    * @type String
1548
 
    * @default 'calendarBase'
1549
 
    * @readOnly
1550
 
    * @protected
1551
 
    * @static
1552
 
    */  
1553
 
  NAME: 'calendarBase',
1554
 
 
1555
 
   /**
1556
 
    * Static property used to define the default attribute configuration of
1557
 
    * the Widget.
1558
 
    *
1559
 
    * @property ATTRS
1560
 
    * @type {Object}
1561
 
    * @protected
1562
 
    * @static
1563
 
    */  
1564
 
  ATTRS: {
1565
 
    tabIndex: {
1566
 
      value: 1
1567
 
    },
1568
 
    /**
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
1572
 
     * calendar.
1573
 
     *
1574
 
     * @attribute date
1575
 
     * @type Date
1576
 
     * @default The first of the month containing today's date, as
1577
 
     * set on the end user's system.
1578
 
     */
1579
 
    date: {
1580
 
      value: new Date(),
1581
 
      setter: function (val) {
1582
 
        var newDate = this._normalizeDate(val);
1583
 
        if (ydate.areEqual(newDate, this.get('date'))) {
1584
 
            return this.get('date');
1585
 
        }
1586
 
        else {
1587
 
            return newDate;
1588
 
        }
1589
 
      }
1590
 
      },
1591
 
 
1592
 
    /**
1593
 
     * A setting specifying whether to shows days from the previous
1594
 
     * month in the visible month's grid, if there are empty preceding
1595
 
     * cells available.
1596
 
     *
1597
 
     * @attribute showPrevMonth
1598
 
     * @type boolean
1599
 
     * @default false
1600
 
     */
1601
 
    showPrevMonth: {
1602
 
      value: false
1603
 
    },
1604
 
 
1605
 
    /**
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.
1609
 
     *
1610
 
     * @attribute showNextMonth
1611
 
     * @type boolean
1612
 
     * @default false
1613
 
     */
1614
 
    showNextMonth: {
1615
 
      value: false
1616
 
    },
1617
 
 
1618
 
    /**
1619
 
     * Strings and properties derived from the internationalization packages
1620
 
     * for the calendar.
1621
 
     *
1622
 
     * @attribute strings
1623
 
     * @type Object
1624
 
     * @protected
1625
 
     */
1626
 
    strings : {
1627
 
            valueFn: function() { return Y.Intl.get("calendar-base"); }
1628
 
        },
1629
 
 
1630
 
    /**
1631
 
     * Custom header renderer for the calendar.
1632
 
     *
1633
 
     * @attribute headerRenderer
1634
 
     * @type String | Function
1635
 
     */
1636
 
        headerRenderer: {
1637
 
            value: "%B %Y"
1638
 
        },
1639
 
 
1640
 
    /**
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.
1644
 
     *
1645
 
     * @attribute enabledDatesRule
1646
 
     * @type String
1647
 
     * @default null
1648
 
     */
1649
 
        enabledDatesRule: {
1650
 
            value: null
1651
 
        },
1652
 
 
1653
 
    /**
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.
1657
 
     *
1658
 
     * @attribute disabledDatesRule
1659
 
     * @type String
1660
 
     * @default null
1661
 
     */
1662
 
        disabledDatesRule: {
1663
 
            value: null
1664
 
        },
1665
 
 
1666
 
    /**
1667
 
     * A read-only attribute providing a list of currently selected dates.
1668
 
     *
1669
 
     * @attribute selectedDates
1670
 
     * @readOnly
1671
 
     * @type Array
1672
 
     */
1673
 
        selectedDates : {
1674
 
          readOnly: true,
1675
 
          getter: function (val) {
1676
 
            return (this._getSelectedDatesList());
1677
 
          }
1678
 
        },
1679
 
 
1680
 
    /**
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.
1684
 
     *
1685
 
     * @attribute customRenderer
1686
 
     * @readOnly
1687
 
     * @type Object
1688
 
     * @default {}
1689
 
     */
1690
 
        customRenderer : {
1691
 
            value: {},
1692
 
            setter: function (val) {
1693
 
                this._rules = val.rules;
1694
 
                this._filterFunction = val.filterFunction;
1695
 
            }
1696
 
        }
1697
 
  }
1698
 
  
1699
 
});
1700
 
 
1701
 
 
1702
 
}, '3.5.0' ,{requires:['widget', 'substitute', 'datatype-date', 'datatype-date-math', 'cssgrids'], lang:['de', 'en', 'fr', 'ja', 'nb-NO', 'pt-BR', 'ru', 'zh-HANT-TW']});