PageSourceSearch

https://www.cm-barreiro.pt/wp-content/themes/gsk-portal-institucio…/vendor/clndr/clndr.min.js?x70148&ver=1.2.4

js cm-barreiro.pt collected 2026-10-02 03:24:36 UTC 56,200 bytes, 1,699 lines download raw bytes

1/**
2 *               ~ CLNDR v1.5.1 ~
3 * ==============================================
4 *       https://github.com/kylestetz/CLNDR
5 * ==============================================
6 *  Created by kyle stetz (github.com/kylestetz)
7 *       & available under the MIT license
8 * http://opensource.org/licenses/mit-license.php
9 * ==============================================
10 *
11 * This is the fully-commented development version of CLNDR.
12 * For the production version, check out clndr.min.js
13 * at https://github.com/kylestetz/CLNDR
14 *
15 * This work is based on the
16 * jQuery lightweight plugin boilerplate
17 * Original author: @ajpiano
18 * Further changes, comments: @addyosmani
19 * Licensed under the MIT license
20 */
21(function (factory) {
22  // Multiple loading methods are supported depending on
23  // what is available globally. While moment is loaded
24  // here, the instance can be passed in at config time.
25  if (typeof define === 'function' && define.amd) {
26    // AMD. Register as an anonymous module.
27    define(['jquery', 'moment'], factory);
28  } else if (typeof exports === 'object') {
29    // Node/CommonJS
30    factory(require('jquery'), require('moment'));
31  } else {
32    // Browser globals
33    factory(jQuery, moment);
34  }
35}(function ($, moment) {
36  // Namespace
37  var pluginName = 'clndr';
38
39  // This is the default calendar template. This can be overridden.
40  var clndrTemplate =
41    "<div class='clndr-controls'>" +
42        "<div class='clndr-control-button'>" +
43            "<span class='clndr-previous-button'>previous</span>" +
44        '</div>' +
45        "<div class='month'><%= month %> <%= year %></div>" +
46        "<div class='clndr-control-button rightalign'>" +
47            "<span class='clndr-next-button'>next</span>" +
48        '</div>' +
49    '</div>' +
50    "<table class='clndr-table' border='0' cellspacing='0' cellpadding='0'>" +
51        '<thead>' +
52            "<tr class='header-days'>" +
53            '<% for(var i = 0; i < daysOfTheWeek.length; i++) { %>' +
54                "<td class='header-day'><%= daysOfTheWeek[i] %></td>" +
55            '<% } %>' +
56            '</tr>' +
57        '</thead>' +
58        '<tbody>' +
59        '<% for(var i = 0; i < numberOfRows; i++){ %>' +
60            '<tr>' +
61            '<% for(var j = 0; j < 7; j++){ %>' +
62            '<% var d = j + i * 7; %>' +
63                "<td class='<%= days[d].classes %>'>" +
64                    "<div class='day-contents'><%= days[d].day %></div>" +
65                '</td>' +
66            '<% } %>' +
67            '</tr>' +
68        '<% } %>' +
69        '</tbody>' +
70    '</table>';
71
72  // Defaults used throughout the application, see docs.
73  var defaults = {
74    events: [],
75    ready: null,
76    extras: null,
77    render: null,
78    moment: null,
79    weekOffset: 0,
80    constraints: null,
81    forceSixRows: null,
82    selectedDate: null,
83    doneRendering: null,
84    daysOfTheWeek: null,
85    multiDayEvents: null,
86    startWithMonth: null,
87    dateParameter: 'date',
88    template: clndrTemplate,
89    showAdjacentMonths: true,
90    trackSelectedDate: false,
91    formatWeekdayHeader: null,
92    adjacentDaysChangeMonth: false,
93    ignoreInactiveDaysInSelection: null,
94    lengthOfTime: {
95      days: null,
96      interval: 1,
97      months: null
98    },
99    clickEvents: {
100      click: null,
101      today: null,
102      nextYear: null,
103      nextMonth: null,
104      nextInterval: null,
105      previousYear: null,
106      onYearChange: null,
107      previousMonth: null,
108      onMonthChange: null,
109      previousInterval: null,
110      onIntervalChange: null
111    },
112    targets: {
113      day: 'day',
114      empty: 'empty',
115      nextButton: 'clndr-next-button',
116      todayButton: 'clndr-today-button',
117      previousButton: 'clndr-previous-button',
118      nextYearButton: 'clndr-next-year-button',
119      previousYearButton: 'clndr-previous-year-button'
120    },
121    classes: {
122      past: 'past',
123      today: 'today',
124      event: 'event',
125      inactive: 'inactive',
126      selected: 'selected',
127      lastMonth: 'last-month',
128      nextMonth: 'next-month',
129      adjacentMonth: 'adjacent-month'
130    }
131  };
132
133  /**
134   * The actual plugin constructor.
135   * Parses the events and lengthOfTime options to build a calendar of day
136   * objects containing event information from the events array.
137   */
138  function Clndr (element, options) {
139    var dayDiff;
140    var constraintEnd;
141    var constraintStart;
142
143    this.element = element;
144
145    // Merge the default options with user-provided options
146    this.options = $.extend(true, {}, defaults, options);
147
148    // Check if moment was passed in as a dependency
149    if (this.options.moment) {
150      moment = this.options.moment;
151    }
152
153    // Validate any correct options
154    this.validateOptions();
155
156    // Boolean values used to log if any contraints are met
157    this.constraints = {
158      next: true,
159      today: true,
160      previous: true,
161      nextYear: true,
162      previousYear: true
163    };
164
165    // If there are events, we should run them through our
166    // addMomentObjectToEvents function which will add a date object that
167    // we can use to make life easier. This is only necessarywhen events
168    // are provided on instantiation, since our setEvents function uses
169    // addMomentObjectToEvents.
170    if (this.options.events.length) {
171      if (this.options.multiDayEvents) {
172        this.options.events =
173          this.addMultiDayMomentObjectsToEvents(this.options.events);
174      } else {
175        this.options.events =
176          this.addMomentObjectToEvents(this.options.events);
177      }
178    }
179
180    // This used to be a place where we'd figure out the current month,
181    // but since we want to open up support for arbitrary lengths of time
182    // we're going to store the current range in addition to the current
183    // month.
184    if (this.options.lengthOfTime.months || this.options.lengthOfTime.days) {
185      // We want to establish intervalStart and intervalEnd, which will
186      // keep track of our boundaries. Let's look at the possibilities...
187      if (this.options.lengthOfTime.months) {
188        // Gonna go right ahead and annihilate any chance for bugs here
189        this.options.lengthOfTime.days = null;
190
191        // The length is specified in months. Is there a start date?
192        if (this.options.lengthOfTime.startDate) {
193          this.intervalStart =
194            moment(this.options.lengthOfTime.startDate).startOf('month');
195        } else if (this.options.startWithMonth) {
196          this.intervalStart =
197            moment(this.options.startWithMonth).startOf('month');
198        } else {
199          this.intervalStart = moment().startOf('month');
200        }
201
202        // Subtract a day so that we are at the end of the interval. We
203        // always want intervalEnd to be inclusive.
204        this.intervalEnd = moment(this.intervalStart)
205          .add(this.options.lengthOfTime.months, 'months')
206          .subtract(1, 'days');
207        this.month = this.intervalStart.clone();
208      } else if (this.options.lengthOfTime.days) {
209        // The length is specified in days. Start date?
210        if (this.options.lengthOfTime.startDate) {
211          this.intervalStart =
212            moment(this.options.lengthOfTime.startDate).startOf('day');
213        } else {
214          this.intervalStart = moment()
215            .weekday(this.options.weekOffset)
216            .startOf('day');
217        }
218
219        this.intervalEnd = moment(this.intervalStart)
220          .add(this.options.lengthOfTime.days - 1, 'days')
221          .endOf('day');
222        this.month = this.intervalStart.clone();
223      }
224    // No length of time specified so we're going to default into using the
225    // current month as the time period.
226    } else {
227      this.month = moment().startOf('month');
228      this.intervalStart = moment(this.month);
229      this.intervalEnd = moment(this.month).endOf('month');
230    }
231
232    if (this.options.startWithMonth) {
233      this.month = moment(this.options.startWithMonth).startOf('month');
234      this.intervalStart = moment(this.month);
235      this.intervalEnd = this.options.lengthOfTime.days
236        ? moment(this.month)
237          .add(this.options.lengthOfTime.days - 1, 'days')
238          .endOf('day')
239        : moment(this.month).endOf('month');
240    }
241
242    // If we've got constraints set, make sure the interval is within them.
243    if (this.options.constraints) {
244      // First check if the startDate exists & is later than now.
245      if (this.options.constraints.startDate) {
246        constraintStart = moment(this.options.constraints.startDate);
247
248        // We need to handle the constraints differently for weekly
249        // calendars vs. monthly calendars.
250        if (this.options.lengthOfTime.days) {
251          if (this.intervalStart.isBefore(constraintStart, 'week')) {
252            this.intervalStart = constraintStart.startOf('week');
253          }
254
255          // If the new interval period is less than the desired length
256          // of time, or before the starting interval, then correct it.
257          dayDiff = this.intervalStart.diff(this.intervalEnd, 'days');
258
259          if (dayDiff < this.options.lengthOfTime.days ||
260              this.intervalEnd.isBefore(this.intervalStart)
261          ) {
262            this.intervalEnd = moment(this.intervalStart)
263              .add(this.options.lengthOfTime.days - 1, 'days')
264              .endOf('day');
265            this.month = this.intervalStart.clone();
266          }
267        } else {
268          if (this.intervalStart.isBefore(constraintStart, 'month')) {
269            // Try to preserve the date by moving only the month.
270            this.intervalStart
271              .set('month', constraintStart.month())
272              .set('year', constraintStart.year());
273            this.month
274              .set('month', constraintStart.month())
275              .set('year', constraintStart.year());
276          }
277
278          // Check if the ending interval is earlier than now.
279          if (this.intervalEnd.isBefore(constraintStart, 'month')) {
280            this.intervalEnd
281              .set('month', constraintStart.month())
282              .set('year', constraintStart.year());
283          }
284        }
285      }
286
287      // Make sure the intervalEnd is before the endDate.
288      if (this.options.constraints.endDate) {
289        constraintEnd = moment(this.options.constraints.endDate);
290
291        // We need to handle the constraints differently for weekly
292        // calendars vs. monthly calendars.
293        if (this.options.lengthOfTime.days) {
294          // The starting interval is after our ending constraint.
295          if (this.intervalStart.isAfter(constraintEnd, 'week')) {
296            this.intervalStart = moment(constraintEnd)
297              .endOf('week')
298              .subtract(this.options.lengthOfTime.days - 1, 'days')
299              .startOf('day');
300            this.intervalEnd = moment(constraintEnd)
301              .endOf('week');
302            this.month = this.intervalStart.clone();
303          }
304        } else {
305          if (this.intervalEnd.isAfter(constraintEnd, 'month')) {
306            this.intervalEnd
307              .set('month', constraintEnd.month())
308              .set('year', constraintEnd.year());
309            this.month
310              .set('month', constraintEnd.month())
311              .set('year', constraintEnd.year());
312          }
313
314          // Check if the starting interval is later than the ending.
315          if (this.intervalStart.isAfter(constraintEnd, 'month')) {
316            this.intervalStart
317              .set('month', constraintEnd.month())
318              .set('year', constraintEnd.year());
319          }
320        }
321      }
322    }
323
324    this._defaults = defaults;
325    this._name = pluginName;
326
327    // Some first-time initialization -> day of the week offset, template
328    // compiling, making and storing some elements we'll need later, and
329    // event handling for the controller.
330    this.init();
331  }
332
333  /**
334   * Calendar initialization.
335   * Sets up the days of the week, the rendering function, binds all of the
336   * events to the rendered calendar, and then stores the node locally.
337   */
338  Clndr.prototype.init = function () {
339    var i;
340    var formatWeekday;
341
342    // Create the days of the week using moment's current language setting
343    this.daysOfTheWeek = this.options.daysOfTheWeek || [];
344
345    // User can supply an optional function to format the weekday header
346    formatWeekday = this.options.formatWeekdayHeader || formatWeekday;
347
348    if (!this.options.daysOfTheWeek) {
349      this.daysOfTheWeek = [];
350
351      formatWeekday = this.options.formatWeekdayHeader || function (day) {
352        return day.format('dd').charAt(0);
353      };
354
355      for (i = 0; i < 7; i++) {
356        this.daysOfTheWeek.push(formatWeekday(moment().weekday(i)));
357      }
358    }
359
360    // Shuffle the week if there's an offset
361    if (this.options.weekOffset) {
362      this.daysOfTheWeek = this.shiftWeekdayLabels(this.options.weekOffset);
363    }
364
365    // Quick and dirty test to make sure rendering is possible.
366    if (!$.isFunction(this.options.render)) {
367      this.options.render = null;
368
369      if (typeof _ === 'undefined') {
370        throw new Error(
371          'Underscore was not found. Please include underscore.js ' +
372          'OR provide a custom render function.');
373      } else {
374        // We're just going ahead and using underscore here if no
375        // render method has been supplied.
376        this.compiledClndrTemplate = _.template(this.options.template);
377      }
378    }
379
380    // Create the parent element that will hold the plugin and save it
381    // for later
382    $(this.element).html("<div class='clndr'></div>");
383    this.calendarContainer = $('.clndr', this.element);
384
385    // Attach event handlers for clicks on buttons/cells
386    this.bindEvents();
387
388    // Do a normal render of the calendar template
389    this.render();
390
391    // If a ready callback has been provided, call it.
392    if (this.options.ready) {
393      this.options.ready.apply(this, []);
394    }
395  };
396
397  Clndr.prototype.validateOptions = function () {
398    // Fix the week offset. It must be between 0 (Sunday) and 6 (Saturday)
399    if (this.options.weekOffset > 6 || this.options.weekOffset < 0) {
400      console.warn(
401        'clndr.js: An invalid offset ' + this.options.weekOffset +
402        ' was provided (must be 0 - 6); using 0 instead.');
403      this.options.weekOffset = 0;
404    }
405  };
406
407  Clndr.prototype.shiftWeekdayLabels = function (offset) {
408    var i;
409    var days = this.daysOfTheWeek;
410
411    for (i = 0; i < offset; i++) {
412      days.push(days.shift());
413    }
414
415    return days;
416  };
417
418  /**
419   * This is where the magic happens. Given a starting date and ending date,
420   * an array of calendarDay objects is constructed that contains appropriate
421   * events and classes depending on the circumstance.
422   */
423  Clndr.prototype.createDaysObject = function (startDate, endDate) {
424    var i;
425    var day;
426    var diff;
427    var dateIterator;
428    // This array will hold numbers for the entire grid (even the blank spaces)
429    var daysArray = [];
430    var endOfNextMonth;
431    var endOfLastMonth;
432    var startOfNextMonth;
433    var startOfLastMonth;
434    var date = startDate.clone();
435    var lengthOfInterval = endDate.diff(startDate, 'days');
436
437    // This is a helper object so that days can resolve their classes
438    // correctly. Don't use it for anything please.
439    this._currentIntervalStart = startDate.clone();
440
441    // Filter the events list (if it exists) to events that are happening
442    // last month, this month and next month (within the current grid view).
443    this.eventsLastMonth = [];
444    this.eventsNextMonth = [];
445    this.eventsThisInterval = [];
446
447    // Event parsing
448    if (this.options.events.length) {
449      // Here are the only two cases where we don't get an event in our
450      // interval:
451      //   startDate | endDate | e.start   | e.end
452      //   e.start   | e.end   | startDate | endDate
453      this.eventsThisInterval = $(this.options.events).filter(
454        function () {
455          var afterEnd = this._clndrStartDateObject.isAfter(endDate);
456          var beforeStart = this._clndrEndDateObject.isBefore(startDate);
457
458          if (beforeStart || afterEnd) {
459            return false;
460          }
461          return true;
462        }).toArray();
463
464      if (this.options.showAdjacentMonths) {
465        startOfLastMonth = startDate.clone()
466          .subtract(1, 'months')
467          .startOf('month');
468        endOfLastMonth = startOfLastMonth.clone().endOf('month');
469        startOfNextMonth = endDate.clone()
470          .add(1, 'months')
471          .startOf('month');
472        endOfNextMonth = startOfNextMonth.clone().endOf('month');
473
474        this.eventsLastMonth = $(this.options.events).filter(
475          function () {
476            var beforeStart = this._clndrEndDateObject
477              .isBefore(startOfLastMonth);
478            var afterEnd = this._clndrStartDateObject
479              .isAfter(endOfLastMonth);
480
481            return !(beforeStart || afterEnd);
482          }).toArray();
483
484        this.eventsNextMonth = $(this.options.events).filter(
485          function () {
486            var beforeStart = this._clndrEndDateObject
487              .isBefore(startOfNextMonth);
488            var afterEnd = this._clndrStartDateObject
489              .isAfter(endOfNextMonth);
490
491            return !(beforeStart || afterEnd);
492          }).toArray();
493      }
494    }
495
496    // If diff is greater than 0, we'll have to fill in last days of the
497    // previous month to account for the empty boxes in the grid. We also
498    // need to take into account the weekOffset parameter. None of this
499    // needs to happen if the interval is being specified in days rather
500    // than months.
501    if (!this.options.lengthOfTime.days) {
502      diff = date.weekday() - this.options.weekOffset;
503
504      if (diff < 0) {
505        diff += 7;
506      }
507
508      if (this.options.showAdjacentMonths) {
509        for (i = 1; i <= diff; i++) {
510          day = moment([
511            startDate.year(),
512            startDate.month(),
513            i
514          ]).subtract(diff, 'days');
515          daysArray.push(
516            this.createDayObject(
517              day,
518              this.eventsLastMonth
519            ));
520        }
521      } else {
522        for (i = 0; i < diff; i++) {
523          daysArray.push(
524            this.calendarDay({
525              classes: this.options.targets.empty +
526                ' ' + this.options.classes.lastMonth
527            }));
528        }
529      }
530    }
531
532    // Now we push all of the days in the interval
533    dateIterator = startDate.clone();
534
535    while (dateIterator.isBefore(endDate) || dateIterator.isSame(endDate, 'day')) {
536      daysArray.push(
537        this.createDayObject(
538          dateIterator.clone(),
539          this.eventsThisInterval
540        ));
541      dateIterator.add(1, 'days');
542    }
543
544    // ...and if there are any trailing blank boxes, fill those in with the
545    // next month first days. Again, we can ignore this if the interval is
546    // specified in days.
547    if (!this.options.lengthOfTime.days) {
548      while (daysArray.length % 7 !== 0) {
549        if (this.options.showAdjacentMonths) {
550          daysArray.push(
551            this.createDayObject(
552              dateIterator.clone(),
553              this.eventsNextMonth
554            ));
555        } else {
556          daysArray.push(
557            this.calendarDay({
558              classes: this.options.targets.empty + ' ' +
559                this.options.classes.nextMonth
560            }));
561        }
562        dateIterator.add(1, 'days');
563      }
564    }
565
566    // If we want to force six rows of calendar, now's our Last Chance to
567    // add another row. If the 42 seems explicit it's because we're
568    // creating a 7-row grid and 6 rows of 7 is always 42!
569    if (this.options.forceSixRows && daysArray.length !== 42) {
570      while (daysArray.length < 42) {
571        if (this.options.showAdjacentMonths) {
572          daysArray.push(
573            this.createDayObject(
574              dateIterator.clone(),
575              this.eventsNextMonth
576            ));
577          dateIterator.add(1, 'days');
578        } else {
579          daysArray.push(this.calendarDay({
580            classes: this.options.targets.empty + ' ' +
581              this.options.classes.nextMonth
582          }));
583        }
584      }
585    }
586
587    return daysArray;
588  };
589
590  Clndr.prototype.createDayObject = function (day, monthEvents) {
591    var end;
592    var j = 0;
593    var start;
594    var dayEnd;
595    var endMoment;
596    var startMoment;
597    var selectedMoment;
598    var now = moment();
599    var eventsToday = [];
600    var extraClasses = '';
601    var properties = {
602      isToday: false,
603      isInactive: false,
604      isAdjacentMonth: false
605    };
606
607    // Validate moment date
608    if (!day.isValid() && day.hasOwnProperty('_d') && day._d !== undefined) {
609      day = moment(day._d);
610    }
611
612    // Set to the end of the day for comparisons
613    dayEnd = day.clone().endOf('day');
614
615    for (j; j < monthEvents.length; j++) {
616      // Keep in mind that the events here already passed the month/year
617      // test. Now all we have to compare is the moment.date(), which
618      // returns the day of the month.
619      start = monthEvents[j]._clndrStartDateObject;
620      end = monthEvents[j]._clndrEndDateObject;
621
622      // If today is the same day as start or is after the start, and
623      // if today is the same day as the end or before the end ...
624      // woohoo semantics!
625      if (start <= dayEnd && day <= end) {
626        eventsToday.push(monthEvents[j]);
627      }
628    }
629
630    if (now.format('YYYY-MM-DD') === day.format('YYYY-MM-DD')) {
631      extraClasses += (' ' + this.options.classes.today);
632      properties.isToday = true;
633    }
634
635    if (day.isBefore(now, 'day')) {
636      extraClasses += (' ' + this.options.classes.past);
637    }
638
639    if (eventsToday.length) {
640      extraClasses += (' ' + this.options.classes.event);
641    }
642
643    if (!this.options.lengthOfTime.days) {
644      if (this._currentIntervalStart.month() > day.month()) {
645        extraClasses += (' ' + this.options.classes.adjacentMonth);
646        properties.isAdjacentMonth = true;
647
648        this._currentIntervalStart.year() === day.year()
649          ? extraClasses += (' ' + this.options.classes.lastMonth)
650          : extraClasses += (' ' + this.options.classes.nextMonth);
651      } else if (this._currentIntervalStart.month() < day.month()) {
652        extraClasses += (' ' + this.options.classes.adjacentMonth);
653        properties.isAdjacentMonth = true;
654
655        this._currentIntervalStart.year() === day.year()
656          ? extraClasses += (' ' + this.options.classes.nextMonth)
657          : extraClasses += (' ' + this.options.classes.lastMonth);
658      }
659    }
660
661    // If there are constraints, we need to add the inactive class to the
662    // days outside of them
663    if (this.options.constraints) {
664      endMoment = moment(this.options.constraints.endDate);
665      startMoment = moment(this.options.constraints.startDate);
666
667      if (this.options.constraints.startDate && day.isBefore(startMoment)) {
668        extraClasses += (' ' + this.options.classes.inactive);
669        properties.isInactive = true;
670      }
671
672      if (this.options.constraints.endDate && day.isAfter(endMoment)) {
673        extraClasses += (' ' + this.options.classes.inactive);
674        properties.isInactive = true;
675      }
676    }
677
678    // Validate moment date
679    if (!day.isValid() && day.hasOwnProperty('_d') && day._d !== undefined) {
680      day = moment(day._d);
681    }
682
683    // Check whether the day is "selected"
684    selectedMoment = moment(this.options.selectedDate);
685
686    if (this.options.selectedDate && day.isSame(selectedMoment, 'day')) {
687      extraClasses += (' ' + this.options.classes.selected);
688    }
689
690    // We're moving away from using IDs in favor of classes, since when
691    // using multiple calendars on a page we are technically violating the
692    // uniqueness of IDs.
693    extraClasses += ' calendar-day-' + day.format('YYYY-MM-DD');
694    // Day of week
695    extraClasses += ' calendar-dow-' + day.weekday();
696
697    return this.calendarDay({
698      date: day,
699      day: day.date(),
700      events: eventsToday,
701      properties: properties,
702      classes: this.options.targets.day + extraClasses
703    });
704  };
705
706  /**
707   * Renders the calendar.
708   *
709   * Get rid of the previous set of calendar parts. This should handle garbage
710   * collection according to jQuery's docs:
711   *   http://api.jquery.com/empty/
712   * To avoid memory leaks, jQuery removes other constructs such as
713   * data and event handlers from the child elements before removing
714   * the elements themselves.
715   */
716  Clndr.prototype.render = function () {
717    var i;
718    var days;
719    var months;
720    var target;
721    var data = {};
722    var end = null;
723    var start = null;
724    var numberOfRows;
725    var eventsThisInterval;
726    var currentIntervalEnd;
727    var currentIntervalStart;
728    var oneYearFromEnd = this.intervalEnd.clone().add(1, 'years');
729    var oneYearAgo = this.intervalStart.clone().subtract(1, 'years');
730
731    this.calendarContainer.empty();
732
733    if (this.options.lengthOfTime.days) {
734      days = this.createDaysObject(
735        this.intervalStart.clone(),
736        this.intervalEnd.clone());
737      data = {
738        days: days,
739        months: [],
740        year: null,
741        month: null,
742        eventsLastMonth: [],
743        eventsNextMonth: [],
744        eventsThisMonth: [],
745        extras: this.options.extras,
746        daysOfTheWeek: this.daysOfTheWeek,
747        intervalEnd: this.intervalEnd.clone(),
748        numberOfRows: Math.ceil(days.length / 7),
749        intervalStart: this.intervalStart.clone(),
750        eventsThisInterval: this.eventsThisInterval
751      };
752    } else if (this.options.lengthOfTime.months) {
753      months = [];
754      numberOfRows = 0;
755      eventsThisInterval = [];
756
757      for (i = 0; i < this.options.lengthOfTime.months; i++) {
758        currentIntervalStart = this.intervalStart
759          .clone()
760          .add(i, 'months');
761        currentIntervalEnd = currentIntervalStart
762          .clone()
763          .endOf('month');
764        days = this.createDaysObject(currentIntervalStart, currentIntervalEnd);
765        // Save events processed for each month into a master array of
766        // events for this interval
767        eventsThisInterval.push(this.eventsThisInterval);
768        months.push({
769          days: days,
770          month: currentIntervalStart
771        });
772      }
773
774            // Get the total number of rows across all months
775      for (i = 0; i < months.length; i++) {
776        numberOfRows += Math.ceil(months[i].days.length / 7);
777      }
778
779      data = {
780        days: [],
781        year: null,
782        month: null,
783        months: months,
784        eventsThisMonth: [],
785        numberOfRows: numberOfRows,
786        extras: this.options.extras,
787        intervalEnd: this.intervalEnd,
788        intervalStart: this.intervalStart,
789        daysOfTheWeek: this.daysOfTheWeek,
790        eventsLastMonth: this.eventsLastMonth,
791        eventsNextMonth: this.eventsNextMonth,
792        eventsThisInterval: eventsThisInterval
793      };
794    } else {
795      // Get an array of days and blank spaces
796      days = this.createDaysObject(
797        this.month.clone().startOf('month'),
798        this.month.clone().endOf('month')
799      );
800
801      data = {
802        days: days,
803        months: [],
804        intervalEnd: null,
805        intervalStart: null,
806        year: this.month.year(),
807        eventsThisInterval: null,
808        extras: this.options.extras,
809        month: this.month.format('MMMM'),
810        daysOfTheWeek: this.daysOfTheWeek,
811        eventsLastMonth: this.eventsLastMonth,
812        eventsNextMonth: this.eventsNextMonth,
813        numberOfRows: Math.ceil(days.length / 7),
814        eventsThisMonth: this.eventsThisInterval
815      };
816    }
817
818    // Render the calendar with the data above & bind events to its elements
819    if (this.options.render) {
820      this.calendarContainer.html(this.options.render.apply(this, [data]));
821    } else {
822      this.calendarContainer.html(this.compiledClndrTemplate(data));
823    }
824
825    // If there are constraints, we need to add the 'inactive' class to
826    // the controls.
827    if (this.options.constraints) {
828      // In the interest of clarity we're just going to remove all
829      // inactive classes and re-apply them each render.
830      for (target in this.options.targets) {
831        if (target !== 'day') {
832          this.element
833            .find('.' + this.options.targets[target])
834            .toggleClass(this.options.classes.inactive, false);
835        }
836      }
837
838      // Just like the classes we'll set this internal state to true and
839      // handle the disabling below.
840      for (i in this.constraints) {
841        this.constraints[i] = true;
842      }
843
844      if (this.options.constraints.startDate) {
845        start = moment(this.options.constraints.startDate);
846      }
847
848      if (this.options.constraints.endDate) {
849        end = moment(this.options.constraints.endDate);
850      }
851
852      // Deal with the month controls first. Do we have room to go back?
853      if (start &&
854          (start.isAfter(this.intervalStart) ||
855            start.isSame(this.intervalStart, 'day'))
856      ) {
857        this.element.find('.' + this.options.targets.previousButton)
858          .toggleClass(this.options.classes.inactive, true);
859          this.constraints.previous = !this.constraints.previous;
860          var prevBtn = $(".clndr__controls__btn--prev");
861            prevBtn.hasClass('clndr__controls__btn--inactive') ? prevBtn.find('button').attr('disabled', true) : null;
862      }
863
864      // Do we have room to go forward?
865      if (end &&
866          (end.isBefore(this.intervalEnd) ||
867            end.isSame(this.intervalEnd, 'day'))
868      ) {
869        this.element.find('.' + this.options.targets.nextButton)
870          .toggleClass(this.options.classes.inactive, true);
871          this.constraints.next = !this.constraints.next;
872          var nextBtn = $(".clndr__controls__btn--next");
873            nextBtn.hasClass('clndr__controls__btn--inactive') ? nextBtn.find('button').attr('disabled', true) : null;
874      }
875
876      // What's last year looking like?
877      if (start && start.isAfter(oneYearAgo)) {
878        this.element
879          .find('.' + this.options.targets.previousYearButton)
880          .toggleClass(this.options.classes.inactive, true);
881        this.constraints.previousYear = !this.constraints.previousYear;
882      }
883
884      // How about next year?
885      if (end && end.isBefore(oneYearFromEnd)) {
886        this.element
887          .find('.' + this.options.targets.nextYearButton)
888          .toggleClass(this.options.classes.inactive, true);
889        this.constraints.nextYear = !this.constraints.nextYear;
890      }
891
892      // Today? We could put this in init(), but we want to support the
893      // user changing the constraints on a living instance.
894      if ((start && start.isAfter(moment(), 'month')) ||
895          (end && end.isBefore(moment(), 'month'))
896      ) {
897        this.element.find('.' + this.options.targets.today)
898          .toggleClass(this.options.classes.inactive, true);
899        this.constraints.today = !this.constraints.today;
900      }
901    }
902
903    if (this.options.doneRendering) {
904      this.options.doneRendering.apply(this, []);
905    }
906  };
907
908    var prevSelectedDay = null;
909  Clndr.prototype.bindEvents = function () {
910    var data = {};
911    var self = this;
912    var $container = $(this.element);
913    var targets = this.options.targets;
914    var classes = self.options.classes;
915    var eventType = this.options.useTouchEvents === true
916      ? 'touchstart'
917      : 'click';
918    var eventName = eventType + '.clndr';
919
920    // Make sure we don't already have events
921    $container
922      .off(eventName, '.' + targets.day)
923      .off(eventName, '.' + targets.empty)
924      .off(eventName, '.' + targets.nextButton)
925      .off(eventName, '.' + targets.todayButton)
926      .off(eventName, '.' + targets.previousButton)
927      .off(eventName, '.' + targets.nextYearButton)
928      .off(eventName, '.' + targets.previousYearButton);
929
930    // Target the day elements and give them click events
931    $container.on(eventName, '.' + targets.day, function (event) {
932      var target;
933        var $currentTarget = $(event.currentTarget);
934        prevSelectedDay != null ? prevSelectedDay.attr('aria-selected', 'false') : null;
935        $currentTarget.attr('aria-selected', 'true');
936        prevSelectedDay = $currentTarget;
937
938      // If adjacentDaysChangeMonth is on, we need to change the
939      // month here. Forward and Back trigger render() to be called.
940      // This is a callback because it can be triggered in two places.
941      var handleAdjacentDay = function () {
942        if (self.options.adjacentDaysChangeMonth) {
943          if ($currentTarget.is('.' + classes.lastMonth)) {
944            self.backActionWithContext(self);
945            return true;
946          } else if ($currentTarget.is('.' + classes.nextMonth)) {
947            self.forwardActionWithContext(self);
948            return true;
949          }
950        }
951      };
952
953      // If setting is enabled, we want to store the selected date
954      // as a string. When render() is called, the selected date will
955      // get the additional classes added. If there is no re-render,
956      // then just add the classes manually.
957      if (self.options.trackSelectedDate &&
958          !(self.options.ignoreInactiveDaysInSelection &&
959            $currentTarget.hasClass(classes.inactive))
960      ) {
961        // If there was no re-render, manually update classes
962        if (handleAdjacentDay() !== true) {
963          // Remember new selected date
964          self.options.selectedDate =
965            self.getTargetDateString(event.currentTarget);
966          $container.find('.' + classes.selected)
967            .removeClass(classes.selected);
968            $currentTarget.addClass(classes.selected);        }
969      } else {
970        handleAdjacentDay();
971      }
972
973      // Trigger click events after any selected date updates
974      if (self.options.clickEvents.click) {
975        target = self.buildTargetObject(event.currentTarget, true);
976        self.options.clickEvents.click.apply(self, [target]);
977      }
978    });
979
980    // Target the empty calendar boxes as well
981    $container.on(eventName, '.' + targets.empty, function (event) {
982      var target;
983      var $eventTarget = $(event.currentTarget);
984
985      if (self.options.clickEvents.click) {
986        target = self.buildTargetObject(event.currentTarget, false);
987        self.options.clickEvents.click.apply(self, [target]);
988      }
989
990      if (self.options.adjacentDaysChangeMonth) {
991        if ($eventTarget.is('.' + classes.lastMonth)) {
992          self.backActionWithContext(self);
993        } else if ($eventTarget.is('.' + classes.nextMonth)) {
994          self.forwardActionWithContext(self);
995        }
996      }
997    });
998
999    // Bind the previous, next and today buttons. We pass the current
1000    // context along with the event so that it can update this instance.
1001    data = {
1002      context: this
1003    };
1004
1005    $container
1006      .on(eventName, '.' + targets.todayButton, data, this.todayAction)
1007      .on(eventName, '.' + targets.nextButton, data, this.forwardAction)
1008      .on(eventName, '.' + targets.previousButton, data, this.backAction)
1009      .on(eventName, '.' + targets.nextYearButton, data, this.nextYearAction)
1010      .on(eventName, '.' + targets.previousYearButton, data, this.previousYearAction);
1011  };
1012
1013  /**
1014   * If the user provided a click callback we'd like to give them something
1015   * nice to work with. buildTargetObject takes the DOM element that was
1016   * clicked and returns an object with the DOM element, events, and the date
1017   * (if the latter two exist). Currently it is based on the id, however it'd
1018   * be nice to use a data- attribute in the future.
1019   */
1020  Clndr.prototype.buildTargetObject = function (currentTarget, targetWasDay) {
1021    // This is our default target object, assuming we hit an empty day
1022    // with no events.
1023    var target = {
1024      date: null,
1025      events: [],
1026      element: currentTarget
1027    };
1028    var filterFn;
1029    var dateString;
1030    var targetEndDate;
1031
1032    // Did we click on a day or just an empty box?
1033    if (targetWasDay) {
1034      dateString = this.getTargetDateString(currentTarget);
1035      target.date = dateString
1036        ? moment(dateString)
1037        : null;
1038
1039      // Do we have events?
1040      if (this.options.events) {
1041        // Are any of the events happening today?
1042        if (this.options.multiDayEvents) {
1043          targetEndDate = target.date.clone().endOf('day');
1044          filterFn = function () {
1045            return this._clndrStartDateObject <= targetEndDate &&
1046              target.date <= this._clndrEndDateObject;
1047          };
1048        } else {
1049          filterFn = function () {
1050            return dateString === this._clndrStartDateObject.format('YYYY-MM-DD');
1051          };
1052        }
1053
1054        // Filter the dates down to the ones that match.
1055        target.events = $.makeArray($(this.options.events).filter(filterFn));
1056      }
1057    }
1058
1059    return target;
1060  };
1061
1062  /**
1063   * Get moment date object of the date associated with the given target.
1064   * This method is meant to be called on ".day" elements.
1065   */
1066  Clndr.prototype.getTargetDateString = function (target) {
1067    // Our identifier is in the list of classNames. Find it!
1068    var index = target.className.indexOf('calendar-day-');
1069
1070    if (index !== -1) {
1071      // Our unique identifier is always 23 characters long.
1072      // If this feels a little wonky, that's probably because it is.
1073      // Open to suggestions on how to improve this guy.
1074      return target.className.substring(index + 13, index + 23);
1075    }
1076
1077    return null;
1078  };
1079
1080  /**
1081   * Triggers any applicable events given a change in the calendar's start
1082   * and end dates. ctx contains the current (changed) start and end date,
1083   * orig contains the original start and end dates.
1084   */
1085  Clndr.prototype.triggerEvents = function (ctx, orig) {
1086    var nextYear;
1087    var prevYear;
1088    var nextMonth;
1089    var prevMonth;
1090    var yearChanged;
1091    var monthChanged;
1092    var nextInterval;
1093    var prevInterval;
1094    var intervalChanged;
1095    var monthArg = [moment(ctx.month)];
1096    var timeOpt = ctx.options.lengthOfTime;
1097    var eventsOpt = ctx.options.clickEvents;
1098    var newInt = {
1099      end: ctx.intervalEnd,
1100      start: ctx.intervalStart
1101    };
1102    var intervalArg = [
1103      moment(ctx.intervalStart),
1104      moment(ctx.intervalEnd)
1105    ];
1106
1107    // We want to determine if any of the change conditions have been
1108    // hit and then trigger our events based off that.
1109    nextMonth = newInt.start.isAfter(orig.start) &&
1110      (Math.abs(newInt.start.month() - orig.start.month()) === 1 ||
1111        (orig.start.month() === 11 && newInt.start.month() === 0));
1112    prevMonth = newInt.start.isBefore(orig.start) &&
1113      (Math.abs(orig.start.month() - newInt.start.month()) === 1 ||
1114        (orig.start.month() === 0 && newInt.start.month() === 11));
1115    monthChanged = newInt.start.month() !== orig.start.month() ||
1116        newInt.start.year() !== orig.start.year();
1117    nextYear = newInt.start.year() - orig.start.year() === 1 ||
1118        newInt.end.year() - orig.end.year() === 1;
1119    prevYear = orig.start.year() - newInt.start.year() === 1 ||
1120        orig.end.year() - newInt.end.year() === 1;
1121    yearChanged = newInt.start.year() !== orig.start.year();
1122
1123    // Only configs with a time period will get the interval change event
1124    if (timeOpt.days || timeOpt.months) {
1125      nextInterval = newInt.start.isAfter(orig.start);
1126      prevInterval = newInt.start.isBefore(orig.start);
1127      intervalChanged = nextInterval || prevInterval;
1128
1129      if (nextInterval && eventsOpt.nextInterval) {
1130        eventsOpt.nextInterval.apply(ctx, intervalArg);
1131      }
1132
1133      if (prevInterval && eventsOpt.previousInterval) {
1134        eventsOpt.previousInterval.apply(ctx, intervalArg);
1135      }
1136
1137      if (intervalChanged && eventsOpt.onIntervalChange) {
1138        eventsOpt.onIntervalChange.apply(ctx, intervalArg);
1139      }
1140    } else {
1141      // @V2-todo see https://github.com/kylestetz/CLNDR/issues/225
1142      if (nextMonth && eventsOpt.nextMonth) {
1143        eventsOpt.nextMonth.apply(ctx, monthArg);
1144      }
1145
1146      if (prevMonth && eventsOpt.previousMonth) {
1147        eventsOpt.previousMonth.apply(ctx, monthArg);
1148      }
1149
1150      if (monthChanged && eventsOpt.onMonthChange) {
1151        eventsOpt.onMonthChange.apply(ctx, monthArg);
1152      }
1153
1154      if (nextYear && eventsOpt.nextYear) {
1155        eventsOpt.nextYear.apply(ctx, monthArg);
1156      }
1157
1158      if (prevYear && eventsOpt.previousYear) {
1159        eventsOpt.previousYear.apply(ctx, monthArg);
1160      }
1161
1162      if (yearChanged && eventsOpt.onYearChange) {
1163        eventsOpt.onYearChange.apply(ctx, monthArg);
1164      }
1165    }
1166  };
1167
1168  /**
1169   * Main action to go backward one period. Other methods call these, like
1170   * backAction which proxies jQuery events, and backActionWithContext which
1171   * is an internal method that this library uses.
1172   */
1173  Clndr.prototype.back = function (options /*, ctx */) {
1174    var ctx = arguments[ 1 ] || this;
1175    var timeOpt = ctx.options.lengthOfTime;
1176    var defaults = {
1177      withCallbacks: false
1178    };
1179    var orig = {
1180      end: ctx.intervalEnd.clone(),
1181      start: ctx.intervalStart.clone()
1182    };
1183
1184    // Extend any options
1185    options = $.extend(true, {}, defaults, options);
1186
1187    // Before we do anything, check if any constraints are limiting this
1188    if (!ctx.constraints.previous) {
1189      return ctx;
1190    }
1191
1192    if (timeOpt.days) {
1193      // Shift the interval in days
1194      ctx.intervalStart
1195        .subtract(timeOpt.interval, 'days')
1196        .startOf('day');
1197      ctx.intervalEnd = ctx.intervalStart.clone()
1198        .add(timeOpt.days - 1, 'days')
1199        .endOf('day');
1200      // @V2-todo Useless, but consistent with API
1201      ctx.month = ctx.intervalStart.clone();
1202    } else {
1203      // Shift the interval by a month (or several months)
1204      ctx.intervalStart
1205        .subtract(timeOpt.interval, 'months')
1206        .startOf('month');
1207      ctx.intervalEnd = ctx.intervalStart.clone()
1208        .add(timeOpt.months || timeOpt.interval, 'months')
1209        .subtract(1, 'days')
1210        .endOf('month');
1211      ctx.month = ctx.intervalStart.clone();
1212    }
1213
1214    ctx.render();
1215
1216    if (options.withCallbacks) {
1217      ctx.triggerEvents(ctx, orig);
1218    }
1219
1220    return ctx;
1221  };
1222
1223  Clndr.prototype.backAction = function (event) {
1224      var ctx = event.data.context;
1225        ctx.backActionWithContext(ctx);
1226  };
1227
1228  Clndr.prototype.backActionWithContext = function (ctx) {
1229    ctx.back({
1230      withCallbacks: true
1231    }, ctx);
1232  };
1233
1234  Clndr.prototype.previous = function (options) {
1235    // Alias
1236    return this.back(options);
1237  };
1238
1239  /**
1240   * Main action to go forward one period. Other methods call these, like
1241   * forwardAction which proxies jQuery events, and backActionWithContext
1242   * which is an internal method that this library uses.
1243   */
1244  Clndr.prototype.forward = function (options /*, ctx */) {
1245    var ctx = arguments[1] || this;
1246    var timeOpt = ctx.options.lengthOfTime;
1247    var defaults = {
1248      withCallbacks: false
1249    };
1250    var orig = {
1251      end: ctx.intervalEnd.clone(),
1252      start: ctx.intervalStart.clone()
1253    };
1254
1255    // Extend any options
1256    options = $.extend(true, {}, defaults, options);
1257
1258    // Before we do anything, check if any constraints are limiting this
1259    if (!ctx.constraints.next) {
1260      return ctx;
1261    }
1262
1263    if (ctx.options.lengthOfTime.days) {
1264      // Shift the interval in days
1265      ctx.intervalStart
1266        .add(timeOpt.interval, 'days')
1267        .startOf('day');
1268      ctx.intervalEnd = ctx.intervalStart.clone()
1269        .add(timeOpt.days - 1, 'days')
1270        .endOf('day');
1271      // @V2-todo Useless, but consistent with API
1272      ctx.month = ctx.intervalStart.clone();
1273    } else {
1274      // Shift the interval by a month (or several months)
1275      ctx.intervalStart
1276        .add(timeOpt.interval, 'months')
1277        .startOf('month');
1278      ctx.intervalEnd = ctx.intervalStart.clone()
1279        .add(timeOpt.months || timeOpt.interval, 'months')
1280        .subtract(1, 'days')
1281        .endOf('month');
1282      ctx.month = ctx.intervalStart.clone();
1283    }
1284
1285    ctx.render();
1286
1287    if (options.withCallbacks) {
1288      ctx.triggerEvents(ctx, orig);
1289    }
1290
1291    return ctx;
1292  };
1293
1294  Clndr.prototype.forwardAction = function (event) {
1295      var ctx = event.data.context;
1296      
1297      ctx.forwardActionWithContext(ctx);
1298  };
1299
1300  Clndr.prototype.forwardActionWithContext = function (ctx) {
1301    ctx.forward({
1302      withCallbacks: true
1303    }, ctx);
1304  };
1305
1306  Clndr.prototype.next = function (options) {
1307    // Alias
1308    return this.forward(options);
1309  };
1310
1311  /**
1312   * Main action to go back one year.
1313   */
1314  Clndr.prototype.previousYear = function (options /*, ctx */) {
1315    var ctx = arguments[1] || this;
1316    var defaults = {
1317      withCallbacks: false
1318    };
1319    var orig = {
1320      end: ctx.intervalEnd.clone(),
1321      start: ctx.intervalStart.clone()
1322    };
1323
1324    // Extend any options
1325    options = $.extend(true, {}, defaults, options);
1326
1327    // Before we do anything, check if any constraints are limiting this
1328    if (!ctx.constraints.previousYear) {
1329      return ctx;
1330    }
1331
1332    ctx.month.subtract(1, 'year');
1333    ctx.intervalStart.subtract(1, 'year');
1334    ctx.intervalEnd.subtract(1, 'year');
1335    ctx.render();
1336
1337    if (options.withCallbacks) {
1338      ctx.triggerEvents(ctx, orig);
1339    }
1340
1341    return ctx;
1342  };
1343
1344  Clndr.prototype.previousYearAction = function (event) {
1345    event.data.context.previousYear({
1346      withCallbacks: true
1347    }, event.data.context);
1348  };
1349
1350  /**
1351   * Main action to go forward one year.
1352   */
1353  Clndr.prototype.nextYear = function (options /*, ctx */) {
1354    var ctx = arguments[1] || this;
1355    var defaults = {
1356      withCallbacks: false
1357    };
1358    var orig = {
1359      end: ctx.intervalEnd.clone(),
1360      start: ctx.intervalStart.clone()
1361    };
1362
1363    // Extend any options
1364    options = $.extend(true, {}, defaults, options);
1365
1366    // Before we do anything, check if any constraints are limiting this
1367    if (!ctx.constraints.nextYear) {
1368      return ctx;
1369    }
1370
1371    ctx.month.add(1, 'year');
1372    ctx.intervalStart.add(1, 'year');
1373    ctx.intervalEnd.add(1, 'year');
1374    ctx.render();
1375
1376    if (options.withCallbacks) {
1377      ctx.triggerEvents(ctx, orig);
1378    }
1379
1380    return ctx;
1381  };
1382
1383  Clndr.prototype.nextYearAction = function (event) {
1384    event.data.context.nextYear({
1385      withCallbacks: true
1386    }, event.data.context);
1387  };
1388
1389  Clndr.prototype.today = function (options /*, ctx */) {
1390    var ctx = arguments[1] || this;
1391    var timeOpt = ctx.options.lengthOfTime;
1392    var defaults = {
1393      withCallbacks: false
1394    };
1395    var orig = {
1396      end: ctx.intervalEnd.clone(),
1397      start: ctx.intervalStart.clone()
1398    };
1399
1400    // Extend any options
1401    options = $.extend(true, {}, defaults, options);
1402    // @V2-todo Only used for legacy month view
1403    ctx.month = moment().startOf('month');
1404
1405    if (timeOpt.days) {
1406      // If there was a startDate specified, we should figure out what
1407      // the weekday is and use that as the starting point of our
1408      // interval. If not, go to today.weekday(0).
1409      if (timeOpt.startDate) {
1410        ctx.intervalStart = moment()
1411          .weekday(timeOpt.startDate.weekday())
1412          .startOf('day');
1413      } else {
1414        ctx.intervalStart = moment().weekday(0).startOf('day');
1415      }
1416
1417      ctx.intervalEnd = ctx.intervalStart.clone()
1418        .add(timeOpt.days - 1, 'days')
1419        .endOf('day');
1420    } else {
1421      // Set the intervalStart to this month.
1422      ctx.intervalStart = moment().startOf('month');
1423      ctx.intervalEnd = ctx.intervalStart.clone()
1424        .add(timeOpt.months || timeOpt.interval, 'months')
1425        .subtract(1, 'days')
1426        .endOf('month');
1427    }
1428
1429    // No need to re-render if we didn't change months.
1430    if (!ctx.intervalStart.isSame(orig.start) ||
1431        !ctx.intervalEnd.isSame(orig.end)
1432    ) {
1433      ctx.render();
1434    }
1435
1436    // Fire the today event handler regardless of any change
1437    if (options.withCallbacks) {
1438      if (ctx.options.clickEvents.today) {
1439        ctx.options.clickEvents.today.apply(ctx, [moment(ctx.month)]);
1440      }
1441
1442      ctx.triggerEvents(ctx, orig);
1443    }
1444  };
1445
1446  Clndr.prototype.todayAction = function (event) {
1447    event.data.context.today({
1448      withCallbacks: true
1449    }, event.data.context);
1450  };
1451
1452  /**
1453   * Changes the month. Accepts 0-11 or a full/partial month name.
1454   * e.g. "Jan", "February", "Mar", etc.
1455   */
1456  Clndr.prototype.setMonth = function (newMonth, options) {
1457    var timeOpt = this.options.lengthOfTime;
1458    var orig = {
1459      end: this.intervalEnd.clone(),
1460      start: this.intervalStart.clone()
1461    };
1462
1463    if (timeOpt.days || timeOpt.months) {
1464      console.warn(
1465        'clndr.js: You are using a custom date interval. ' +
1466        'Use Clndr.setIntervalStart(startDate) instead.');
1467      return this;
1468    }
1469
1470    this.month.month(newMonth);
1471    this.intervalStart = this.month.clone().startOf('month');
1472    this.intervalEnd = this.intervalStart.clone().endOf('month');
1473    this.render();
1474
1475    if (options && options.withCallbacks) {
1476      this.triggerEvents(this, orig);
1477    }
1478
1479    return this;
1480  };
1481
1482  Clndr.prototype.setYear = function (newYear, options) {
1483    var orig = {
1484      end: this.intervalEnd.clone(),
1485      start: this.intervalStart.clone()
1486    };
1487
1488    this.month.year(newYear);
1489    this.intervalEnd.year(newYear);
1490    this.intervalStart.year(newYear);
1491    this.render();
1492
1493    if (options && options.withCallbacks) {
1494      this.triggerEvents(this, orig);
1495    }
1496
1497    return this;
1498  };
1499
1500  /**
1501   * Sets the start of the time period according to newDate.
1502   * newDate can be a string or a moment object.
1503   */
1504  Clndr.prototype.setIntervalStart = function (newDate, options) {
1505    var timeOpt = this.options.lengthOfTime;
1506    var orig = {
1507      end: this.intervalEnd.clone(),
1508      start: this.intervalStart.clone()
1509    };
1510
1511    if (!timeOpt.days && !timeOpt.months) {
1512      console.warn(
1513        'clndr.js: You are using a custom date interval. ' +
1514        'Use Clndr.setIntervalStart(startDate) instead.');
1515      return this;
1516    }
1517
1518    if (timeOpt.days) {
1519      this.intervalStart = moment(newDate).startOf('day');
1520      this.intervalEnd = this.intervalStart.clone()
1521        .add(timeOpt.days - 1, 'days')
1522        .endOf('day');
1523    } else {
1524      this.intervalStart = moment(newDate).startOf('month');
1525      this.intervalEnd = this.intervalStart.clone()
1526        .add(timeOpt.months || timeOpt.interval, 'months')
1527        .subtract(1, 'days')
1528        .endOf('month');
1529    }
1530
1531    this.month = this.intervalStart.clone();
1532    this.render();
1533
1534    if (options && options.withCallbacks) {
1535      this.triggerEvents(this, orig);
1536    }
1537
1538    return this;
1539  };
1540
1541  /**
1542   * Overwrites extras in the calendar and triggers a render.
1543   */
1544  Clndr.prototype.setExtras = function (extras) {
1545    this.options.extras = extras;
1546    this.render();
1547
1548    return this;
1549  };
1550
1551  /**
1552   * Overwrites events in the calendar and triggers a render.
1553   */
1554  Clndr.prototype.setEvents = function (events) {
1555    // Go through each event and add a moment object
1556    if (this.options.multiDayEvents) {
1557      this.options.events = this.addMultiDayMomentObjectsToEvents(events);
1558    } else {
1559      this.options.events = this.addMomentObjectToEvents(events);
1560    }
1561
1562    this.render();
1563
1564    return this;
1565  };
1566
1567  /**
1568   * Adds additional events to the calendar and triggers a render.
1569   */
1570  Clndr.prototype.addEvents = function (events /*, reRender */) {
1571    var reRender = arguments.length > 1
1572      ? arguments[1]
1573      : true;
1574
1575    // Go through each event and add a moment object
1576    if (this.options.multiDayEvents) {
1577      this.options.events = $.merge(
1578        this.options.events,
1579        this.addMultiDayMomentObjectsToEvents(events));
1580    } else {
1581      this.options.events = $.merge(
1582        this.options.events,
1583        this.addMomentObjectToEvents(events));
1584    }
1585
1586    if (reRender) {
1587      this.render();
1588    }
1589
1590    return this;
1591  };
1592
1593  /**
1594   * Passes all events through a matching function. Any that pass a truth
1595   * test will be removed from the calendar's events. This triggers a render.
1596   */
1597  Clndr.prototype.removeEvents = function (matchingFn) {
1598    var i;
1599
1600    for (i = this.options.events.length - 1; i >= 0; i--) {
1601      if (matchingFn(this.options.events[i]) === true) {
1602        this.options.events.splice(i, 1);
1603      }
1604    }
1605
1606    this.render();
1607
1608    return this;
1609  };
1610
1611  Clndr.prototype.addMomentObjectToEvents = function (events) {
1612    var i = 0;
1613    var self = this;
1614
1615    for (i; i < events.length; i++) {
1616      // Add the date as both start and end, since it's a single-day
1617      // event by default
1618      events[i]._clndrStartDateObject =
1619        moment(events[i][self.options.dateParameter]);
1620      events[i]._clndrEndDateObject =
1621        moment(events[i][self.options.dateParameter]);
1622    }
1623
1624    return events;
1625  };
1626
1627  Clndr.prototype.addMultiDayMomentObjectsToEvents = function (events) {
1628    var end;
1629    var start;
1630    var i = 0;
1631    var self = this;
1632    var multiEvents = self.options.multiDayEvents;
1633
1634    for (i; i < events.length; i++) {
1635      end = events[i][multiEvents.endDate];
1636      start = events[i][multiEvents.startDate];
1637
1638      // If we don't find the startDate OR endDate fields, look for singleDay
1639      if (!end && !start) {
1640        events[i]._clndrEndDateObject =
1641          moment(events[i][multiEvents.singleDay]);
1642        events[i]._clndrStartDateObject =
1643          moment(events[i][multiEvents.singleDay]);
1644      } else {
1645        // Otherwise use startDate and endDate, or whichever one is present
1646        events[i]._clndrEndDateObject = moment(end || start);
1647        events[i]._clndrStartDateObject = moment(start || end);
1648      }
1649    }
1650
1651    return events;
1652  };
1653
1654  Clndr.prototype.calendarDay = function (options) {
1655    var defaults = {
1656      day: '',
1657      date: null,
1658      events: [],
1659      classes: this.options.targets.empty
1660    };
1661
1662    return $.extend({}, defaults, options);
1663  };
1664
1665  Clndr.prototype.destroy = function () {
1666    var $container = $(this.calendarContainer);
1667
1668    $container.parent().data('plugin_clndr', null);
1669    $container.empty().remove();
1670
1671    this.options = defaults;
1672    this.element = null;
1673  };
1674
1675  $.fn.clndr = function (options) {
1676    var clndrInstance;
1677
1678    if (this.length > 1) {
1679      throw new Error(
1680        'CLNDR does not support multiple elements yet. Make sure ' +
1681        'your clndr selector returns only one element.'
1682      );
1683    }
1684
1685    if (!this.length) {
1686      throw new Error('CLNDR cannot be instantiated on an empty selector.');
1687    }
1688
1689    if (!this.data('plugin_clndr')) {
1690      clndrInstance = new Clndr(this, options);
1691
1692      this.data('plugin_clndr', clndrInstance);
1693
1694      return clndrInstance;
1695    }
1696
1697    return this.data('plugin_clndr');
1698  };
1699}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.