PageSourceSearch

https://gruni.edu.ge/assets/calendar/js/clndr.js

js gruni.edu.ge collected 2026-10-02 07:44:01 UTC 34,042 bytes, 908 lines download raw bytes

1/*
2 *               ~ CLNDR v1.2.4 ~
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
22(function (factory) {
23
24  if (typeof define === 'function' && define.amd) {
25
26    // AMD. Register as an anonymous module.
27    define(['jquery', 'moment'], factory);
28  } else if (typeof exports === 'object') {
29
30    // Node/CommonJS
31    factory(require('jquery'), require('moment'));
32  } else {
33
34    // Browser globals
35    factory(jQuery, moment);
36  }
37
38}(function ($, moment) {
39
40  // This is the default calendar template. This can be overridden.
41  var clndrTemplate = "<div class='clndr-controls'>" +
42    "<div class='clndr-control-button'><span class='clndr-previous-button'>previous</span></div><div class='month'><%= month %> <%= year %></div><div class='clndr-control-button rightalign'><span class='clndr-next-button'>next</span></div>" +
43    "</div>" +
44  "<table class='clndr-table' border='0' cellspacing='0' cellpadding='0'>" +
45    "<thead>" +
46    "<tr class='header-days'>" +
47    "<% for(var i = 0; i < daysOfTheWeek.length; i++) { %>" +
48      "<td class='header-day'><%= daysOfTheWeek[i] %></td>" +
49    "<% } %>" +
50    "</tr>" +
51    "</thead>" +
52    "<tbody>" +
53    "<% for(var i = 0; i < numberOfRows; i++){ %>" +
54      "<tr>" +
55      "<% for(var j = 0; j < 7; j++){ %>" +
56      "<% var d = j + i * 7; %>" +
57      "<td class='<%= days[d].classes %>'><div class='day-contents'><%= days[d].day %>" +
58      "</div></td>" +
59      "<% } %>" +
60      "</tr>" +
61    "<% } %>" +
62    "</tbody>" +
63  "</table>";
64
65  var pluginName = 'clndr';
66
67  var defaults = {
68    template: clndrTemplate,
69    weekOffset: 0,
70    startWithMonth: null,
71    clickEvents: {
72      click: null,
73      nextMonth: null,
74      previousMonth: null,
75      nextYear: null,
76      previousYear: null,
77      today: null,
78      onMonthChange: null,
79      onYearChange: null
80    },
81    targets: {
82      nextButton: 'clndr-next-button',
83      previousButton: 'clndr-previous-button',
84      nextYearButton: 'clndr-next-year-button',
85      previousYearButton: 'clndr-previous-year-button',
86      todayButton: 'clndr-today-button',
87      day: 'day',
88      empty: 'empty'
89    },
90    events: [],
91    extras: null,
92    dateParameter: 'date',
93    multiDayEvents: null,
94    doneRendering: null,
95    render: null,
96    daysOfTheWeek: null,
97    showAdjacentMonths: true,
98    adjacentDaysChangeMonth: false,
99    ready: null,
100    constraints: null,
101    forceSixRows: null
102  };
103
104  // The actual plugin constructor
105  function Clndr( element, options ) {
106    this.element = element;
107
108    // merge the default options with user-provided options
109    this.options = $.extend(true, {}, defaults, options);
110
111    // if there are events, we should run them through our addMomentObjectToEvents function
112    // which will add a date object that we can use to make life easier. This is only necessary
113    // when events are provided on instantiation, since our setEvents function uses addMomentObjectToEvents.
114    if(this.options.events.length) {
115      if(this.options.multiDayEvents) {
116        this.options.events = this.addMultiDayMomentObjectsToEvents(this.options.events);
117      } else {
118        this.options.events = this.addMomentObjectToEvents(this.options.events);
119      }
120    }
121
122    // this object will store a reference to the current month.
123    // it's a moment object, which allows us to poke at it a little if we need to.
124    // this will serve as the basis for switching between months & is the go-to
125    // internally if we want to know which month we're currently at.
126    if(this.options.startWithMonth) {
127      this.month = moment(this.options.startWithMonth).startOf('month');
128    } else {
129      this.month = moment().startOf('month');
130    }
131
132    // if we've got constraints set, make sure the month is within them.
133    if(this.options.constraints) {
134      // first check if the start date exists & is later than now.
135      if(this.options.constraints.startDate) {
136        var startMoment = moment(this.options.constraints.startDate);
137        if(this.month.isBefore(startMoment, 'month')) {
138          this.month.set('month', startMoment.month());
139          this.month.set('year', startMoment.year());
140        }
141      }
142      // make sure the month (whether modified or not) is before the endDate
143      if(this.options.constraints.endDate) {
144        var endMoment = moment(this.options.constraints.endDate);
145        if(this.month.isAfter(endMoment, 'month')) {
146          this.month.set('month', endMoment.month()).set('year', endMoment.year());
147        }
148      }
149    }
150
151    this._defaults = defaults;
152    this._name = pluginName;
153
154    // Some first-time initialization -> day of the week offset,
155    // template compiling, making and storing some elements we'll need later,
156    // & event handling for the controller.
157    this.init();
158  }
159
160  Clndr.prototype.init = function () {
161    // create the days of the week using moment's current language setting
162    this.daysOfTheWeek = this.options.daysOfTheWeek || [];
163    if(!this.options.daysOfTheWeek) {
164      this.daysOfTheWeek = [];
165      for(var i = 0; i < 7; i++) {
166        this.daysOfTheWeek.push( moment().weekday(i).format('ddd') );
167      }
168    }
169    // shuffle the week if there's an offset
170    if(this.options.weekOffset) {
171      this.daysOfTheWeek = this.shiftWeekdayLabels(this.options.weekOffset);
172    }
173
174    // quick & dirty test to make sure rendering is possible.
175    if( !$.isFunction(this.options.render) ) {
176      this.options.render = null;
177      if (typeof _ === 'undefined') {
178        throw new Error("Underscore was not found. Please include underscore.js OR provide a custom render function.");
179      }
180      else {
181        // we're just going ahead and using underscore here if no render method has been supplied.
182        this.compiledClndrTemplate = _.template(this.options.template);
183      }
184    }
185
186    // create the parent element that will hold the plugin & save it for later
187    $(this.element).html("<div class='clndr'></div>");
188    this.calendarContainer = $('.clndr', this.element);
189
190    // attach event handlers for clicks on buttons/cells
191    this.bindEvents();
192
193    // do a normal render of the calendar template
194    this.render();
195
196    // if a ready callback has been provided, call it.
197    if(this.options.ready) {
198      this.options.ready.apply(this, []);
199    }
200  };
201
202  Clndr.prototype.shiftWeekdayLabels = function(offset) {
203    var days = this.daysOfTheWeek;
204    for(var i = 0; i < offset; i++) {
205      days.push( days.shift() );
206    }
207    return days;
208  };
209
210  // This is where the magic happens. Given a moment object representing the current month,
211  // an array of calendarDay objects is constructed that contains appropriate events and
212  // classes depending on the circumstance.
213  Clndr.prototype.createDaysObject = function(currentMonth) {
214    // this array will hold numbers for the entire grid (even the blank spaces)
215    daysArray = [];
216    var date = currentMonth.startOf('month');
217
218    // filter the events list (if it exists) to events that are happening last month, this month and next month (within the current grid view)
219    this.eventsLastMonth = [];
220    this.eventsThisMonth = [];
221    this.eventsNextMonth = [];
222
223    if(this.options.events.length) {
224
225      // MULTI-DAY EVENT PARSING
226      // if we're using multi-day events, the start or end must be in the current month
227      if(this.options.multiDayEvents) {
228        this.eventsThisMonth = $(this.options.events).filter( function() {
229//          return this._clndrStartDateObject.format("YYYY-MM") <= currentMonth.format("YYYY-MM")
230//          || currentMonth.format("YYYY-MM") <= this._clndrEndDateObject.format("YYYY-MM");
231            if ( this._clndrStartDateObject.format("YYYY-MM") === currentMonth.format("YYYY-MM")
232                    || this._clndrEndDateObject.format("YYYY-MM") === currentMonth.format("YYYY-MM") ) {
233                return true;
234            }
235            if ( this._clndrStartDateObject.format("YYYY-MM") <= currentMonth.format("YYYY-MM")
236                    && this._clndrEndDateObject.format("YYYY-MM") >= currentMonth.format("YYYY-MM") ) {
237                return true;
238            }
239
240            return false;
241        }).toArray();
242
243        if(this.options.showAdjacentMonths) {
244          var lastMonth = currentMonth.clone().subtract(1, 'months');
245          var nextMonth = currentMonth.clone().add(1, 'months');
246          this.eventsLastMonth = $(this.options.events).filter( function() {
247//            return this._clndrStartDateObject.format("YYYY-MM") <= lastMonth.format("YYYY-MM")
248//          || lastMonth.format("YYYY-MM") <= this._clndrEndDateObject.format("YYYY-MM");
249            if ( this._clndrStartDateObject.format("YYYY-MM") === lastMonth.format("YYYY-MM")
250                    || this._clndrEndDateObject.format("YYYY-MM") === lastMonth.format("YYYY-MM") ) {
251                return true;
252            }
253            if ( this._clndrStartDateObject.format("YYYY-MM") <= lastMonth.format("YYYY-MM")
254                    && this._clndrEndDateObject.format("YYYY-MM") >= lastMonth.format("YYYY-MM") ) {
255                return true;
256            }
257
258            return false;
259          }).toArray();
260
261          this.eventsNextMonth = $(this.options.events).filter( function() {
262//            return this._clndrStartDateObject.format("YYYY-MM") <= nextMonth.format("YYYY-MM")
263//          || nextMonth.format("YYYY-MM") <= this._clndrEndDateObject.format("YYYY-MM");
264            if ( this._clndrStartDateObject.format("YYYY-MM") === nextMonth.format("YYYY-MM")
265                    || this._clndrEndDateObject.format("YYYY-MM") === nextMonth.format("YYYY-MM") ) {
266                return true;
267            }
268            if ( this._clndrStartDateObject.format("YYYY-MM") <= nextMonth.format("YYYY-MM")
269                    && this._clndrEndDateObject.format("YYYY-MM") >= nextMonth.format("YYYY-MM") ) {
270                return true;
271            }
272
273            return false;
274          }).toArray();
275        }
276      }
277
278      // SINGLE-DAY EVENT PARSING
279      // if we're using single-day events, use _clndrDateObject
280      else {
281        this.eventsThisMonth = $(this.options.events).filter( function() {
282          return this._clndrDateObject.format("YYYY-MM") == currentMonth.format("YYYY-MM");
283        }).toArray();
284
285        // filter the adjacent months as well, if the option is true
286        if(this.options.showAdjacentMonths) {
287          var lastMonth = currentMonth.clone().subtract(1, 'months');
288          var nextMonth = currentMonth.clone().add(1, 'months');
289          this.eventsLastMonth = $(this.options.events).filter( function() {
290            return this._clndrDateObject.format("YYYY-MM") == lastMonth.format("YYYY-MM");
291          }).toArray();
292
293          this.eventsNextMonth = $(this.options.events).filter( function() {
294            return this._clndrDateObject.format("YYYY-MM") == nextMonth.format("YYYY-MM");
295          }).toArray();
296        }
297      }
298    }
299
300    // if diff is greater than 0, we'll have to fill in last days of the previous month
301    // to account for the empty boxes in the grid.
302    // we also need to take into account the weekOffset parameter
303    var diff = date.weekday() - this.options.weekOffset;
304    if(diff < 0) diff += 7;
305
306    if(this.options.showAdjacentMonths) {
307      for(var i = 0; i < diff; i++) {
308        var day = moment([currentMonth.year(), currentMonth.month(), i - diff + 1]);
309        daysArray.push( this.createDayObject(day, this.eventsLastMonth) );
310      }
311    } else {
312      for(var i = 0; i < diff; i++) {
313        daysArray.push( this.calendarDay({ classes: this.options.targets.empty + " last-month" }) );
314      }
315    }
316
317    // now we push all of the days in a month
318    var numOfDays = date.daysInMonth();
319    for(var i = 1; i <= numOfDays; i++) {
320      var day = moment.utc([currentMonth.year(), currentMonth.month(), i]);
321      daysArray.push(this.createDayObject(day, this.eventsThisMonth) )
322    }
323
324    // ...and if there are any trailing blank boxes, fill those in
325    // with the next month first days
326    var i = 1;
327    while(daysArray.length % 7 !== 0) {
328      if(this.options.showAdjacentMonths) {
329        var day = moment.utc([currentMonth.year(), currentMonth.month(), numOfDays + i]);
330        daysArray.push( this.createDayObject(day, this.eventsNextMonth) );
331      } else {
332        daysArray.push( this.calendarDay({ classes: this.options.targets.empty + " next-month" }) );
333      }
334      i++;
335    }
336
337    // if we want to force six rows of calendar, now's our Last Chance to add another row.
338    // if the 42 seems explicit it's because we're creating a 7-row grid and 6 rows of 7 is always 42!
339    if(this.options.forceSixRows && daysArray.length !== 42 ) {
340      var start = moment(daysArray[daysArray.length - 1].date).add(1, 'days');
341      while(daysArray.length < 42) {
342        if(this.options.showAdjacentMonths) {
343          daysArray.push( this.createDayObject(moment(start), this.eventsNextMonth) );
344          start.add(1, 'days');
345        } else {
346          daysArray.push( this.calendarDay({ classes: this.options.targets.empty + " next-month" }) );
347        }
348      }
349    }
350
351    return daysArray;
352  };
353
354  Clndr.prototype.createDayObject = function(day, monthEvents) {
355    var eventsToday = [];
356    var now = moment();
357    var self = this;
358
359    var j = 0, l = monthEvents.length;
360    for(j; j < l; j++) {
361      // keep in mind that the events here already passed the month/year test.
362      // now all we have to compare is the moment.date(), which returns the day of the month.
363      if(self.options.multiDayEvents) {
364        var start = monthEvents[j]._clndrStartDateObject;
365        var end = monthEvents[j]._clndrEndDateObject;
366        // if today is the same day as start or is after the start, and
367        // if today is the same day as the end or before the end ...
368        // woohoo semantics!
369        if( ( day.isSame(start, 'day') || day.isAfter(start, 'day') ) &&
370          ( day.isSame(end, 'day') || day.isBefore(end, 'day') ) ) {
371          eventsToday.push( monthEvents[j] );
372        }
373      } else {
374        if( monthEvents[j]._clndrDateObject.date() == day.date() ) {
375          eventsToday.push( monthEvents[j] );
376        }
377      }
378    }
379
380    var extraClasses = "";
381
382    if(now.format("YYYY-MM-DD") == day.format("YYYY-MM-DD")) {
383       extraClasses += " today";
384    }
385    if(day.isBefore(now, 'day')) {
386      extraClasses += " past";
387    }
388    if(eventsToday.length) {
389       extraClasses += " event";
390    }
391    if(this.month.month() > day.month()) {
392       extraClasses += " adjacent-month";
393
394       this.month.year() === day.year()
395           ? extraClasses += " last-month"
396           : extraClasses += " next-month";
397
398    } else if(this.month.month() < day.month()) {
399       extraClasses += " adjacent-month";
400
401       this.month.year() === day.year()
402           ? extraClasses += " next-month"
403           : extraClasses += " last-month";
404    }
405
406    // if there are constraints, we need to add the inactive class to the days outside of them
407    if(this.options.constraints) {
408      if(this.options.constraints.startDate && day.isBefore(moment( this.options.constraints.startDate ))) {
409        extraClasses += " inactive";
410      }
411      if(this.options.constraints.endDate && day.isAfter(moment( this.options.constraints.endDate ))) {
412        extraClasses += " inactive";
413      }
414    }
415
416    // validate moment date
417    if (!day.isValid() && day.hasOwnProperty('_d') && day._d != undefined) {
418        day = moment(day._d);
419    }
420
421    // we're moving away from using IDs in favor of classes, since when
422    // using multiple calendars on a page we are technically violating the
423    // uniqueness of IDs.
424    extraClasses += " calendar-day-" + day.format("YYYY-MM-DD");
425
426    // day of week
427    extraClasses += " calendar-dow-" + day.weekday();
428
429    return this.calendarDay({
430      day: day.date(),
431      classes: this.options.targets.day + extraClasses,
432      events: eventsToday,
433      date: day
434    });
435  };
436
437  Clndr.prototype.render = function() {
438    // get rid of the previous set of calendar parts.
439    // TODO: figure out if this is the right way to ensure proper garbage collection?
440    this.calendarContainer.children().remove();
441    // get an array of days and blank spaces
442    var days = this.createDaysObject(this.month);
443    // this is to prevent a scope/naming issue between this.month and data.month
444    var currentMonth = this.month;
445
446    var data = {
447      daysOfTheWeek: this.daysOfTheWeek,
448      numberOfRows: Math.ceil(days.length / 7),
449      days: days,
450      month: this.month.format('MMMM'),
451      year: this.month.year(),
452      eventsThisMonth: this.eventsThisMonth,
453      eventsLastMonth: this.eventsLastMonth,
454      eventsNextMonth: this.eventsNextMonth,
455      extras: this.options.extras
456    };
457
458    // render the calendar with the data above & bind events to its elements
459    if(!this.options.render) {
460      this.calendarContainer.html(this.compiledClndrTemplate(data));
461    } else {
462      this.calendarContainer.html(this.options.render.apply(this, [data]));
463    }
464
465    // if there are constraints, we need to add the 'inactive' class to the controls
466    if(this.options.constraints) {
467      // in the interest of clarity we're just going to remove all inactive classes and re-apply them each render.
468      for(var target in this.options.targets) {
469        if(target != this.options.targets.day) {
470          this.element.find('.' + this.options.targets[target]).toggleClass('inactive', false);
471        }
472      }
473
474      var start = null;
475      var end = null;
476
477      if(this.options.constraints.startDate) {
478        start = moment(this.options.constraints.startDate);
479      }
480      if(this.options.constraints.endDate) {
481        end = moment(this.options.constraints.endDate);
482      }
483      // deal with the month controls first.
484      // are we at the start month?
485      if(start && this.month.isSame( start, 'month' )) {
486        this.element.find('.' + this.options.targets.previousButton).toggleClass('inactive', true);
487      }
488      // are we at the end month?
489      if(end && this.month.isSame( end, 'month' )) {
490        this.element.find('.' + this.options.targets.nextButton).toggleClass('inactive', true);
491      }
492      // what's last year looking like?
493      if(start && moment(start).subtract(1, 'years').isBefore(moment(this.month).subtract(1, 'years')) ) {
494        this.element.find('.' + this.options.targets.previousYearButton).toggleClass('inactive', true);
495      }
496      // how about next year?
497      if(end && moment(end).add(1, 'years').isAfter(moment(this.month).add(1, 'years')) ) {
498        this.element.find('.' + this.options.targets.nextYearButton).toggleClass('inactive', true);
499      }
500      // today? we could put this in init(), but we want to support the user changing the constraints on a living instance.
501      if(( start && start.isAfter( moment(), 'month' ) ) || ( end && end.isBefore( moment(), 'month' ) )) {
502        this.element.find('.' + this.options.targets.today).toggleClass('inactive', true);
503      }
504    }
505
506
507    if(this.options.doneRendering) {
508      this.options.doneRendering.apply(this, []);
509    }
510  };
511
512  Clndr.prototype.bindEvents = function() {
513    var $container = $(this.element);
514    var self = this;
515
516    // target the day elements and give them click events
517    $container.on('click', '.'+this.options.targets.day, function(event) {
518      if(self.options.clickEvents.click) {
519        var target = self.buildTargetObject(event.currentTarget, true);
520        self.options.clickEvents.click.apply(self, [target]);
521      }
522      // if adjacentDaysChangeMonth is on, we need to change the month here.
523      if(self.options.adjacentDaysChangeMonth) {
524        if($(event.currentTarget).is(".last-month")) {
525          self.backActionWithContext(self);
526        } else if($(event.currentTarget).is(".next-month")) {
527          self.forwardActionWithContext(self);
528        }
529      }
530    });
531    // target the empty calendar boxes as well
532    $container.on('click', '.'+this.options.targets.empty, function(event) {
533      if(self.options.clickEvents.click) {
534        var target = self.buildTargetObject(event.currentTarget, false);
535        self.options.clickEvents.click.apply(self, [target]);
536      }
537      if(self.options.adjacentDaysChangeMonth) {
538        if($(event.currentTarget).is(".last-month")) {
539          self.backActionWithContext(self);
540        } else if($(event.currentTarget).is(".next-month")) {
541          self.forwardActionWithContext(self);
542        }
543      }
544    });
545
546    // bind the previous, next and today buttons
547    $container
548      .on('click', '.'+this.options.targets.previousButton, { context: this }, this.backAction)
549      .on('click', '.'+this.options.targets.nextButton, { context: this }, this.forwardAction)
550      .on('click', '.'+this.options.targets.todayButton, { context: this }, this.todayAction)
551      .on('click', '.'+this.options.targets.nextYearButton, { context: this }, this.nextYearAction)
552      .on('click', '.'+this.options.targets.previousYearButton, { context: this }, this.previousYearAction);
553  }
554
555  // If the user provided a click callback we'd like to give them something nice to work with.
556  // buildTargetObject takes the DOM element that was clicked and returns an object with
557  // the DOM element, events, and the date (if the latter two exist). Currently it is based on the id,
558  // however it'd be nice to use a data- attribute in the future.
559  Clndr.prototype.buildTargetObject = function(currentTarget, targetWasDay) {
560    // This is our default target object, assuming we hit an empty day with no events.
561    var target = {
562      element: currentTarget,
563      events: [],
564      date: null
565    };
566    // did we click on a day or just an empty box?
567    if(targetWasDay) {
568      var dateString;
569
570      // Our identifier is in the list of classNames. Find it!
571      var classNameIndex = currentTarget.className.indexOf('calendar-day-');
572      if(classNameIndex !== 0) {
573        // our unique identifier is always 23 characters long.
574        // If this feels a little wonky, that's probably because it is.
575        // Open to suggestions on how to improve this guy.
576        dateString = currentTarget.className.substring(classNameIndex + 13, classNameIndex + 23);
577        target.date = moment(dateString);
578      } else {
579        target.date = null;
580      }
581
582      // do we have events?
583      if(this.options.events) {
584        // are any of the events happening today?
585        if(this.options.multiDayEvents) {
586          target.events = $.makeArray( $(this.options.events).filter( function() {
587            // filter the dates down to the ones that match.
588            return ( ( target.date.isSame(this._clndrStartDateObject, 'day') || target.date.isAfter(this._clndrStartDateObject, 'day') ) &&
589              ( target.date.isSame(this._clndrEndDateObject, 'day') || target.date.isBefore(this._clndrEndDateObject, 'day') ) );
590          }) );
591        } else {
592          target.events = $.makeArray( $(this.options.events).filter( function() {
593            // filter the dates down to the ones that match.
594            return this._clndrDateObject.format('YYYY-MM-DD') == dateString;
595          }) );
596        }
597      }
598    }
599
600    return target;
601  }
602
603  // the click handlers in bindEvents need a context, so these are wrappers
604  // to the actual functions. Todo: better way to handle this?
605  Clndr.prototype.forwardAction = function(event) {
606    var self = event.data.context;
607    self.forwardActionWithContext(self);
608  };
609
610  Clndr.prototype.backAction = function(event) {
611    var self = event.data.context;
612    self.backActionWithContext(self);
613  };
614
615  // These are called directly, except for in the bindEvent click handlers,
616  // where forwardAction and backAction proxy to these guys.
617  Clndr.prototype.backActionWithContext = function(self) {
618    // before we do anything, check if there is an inactive class on the month control.
619    // if it does, we want to return and take no action.
620    if(self.element.find('.' + self.options.targets.previousButton).hasClass('inactive')) {
621      return;
622    }
623
624    // is subtracting one month going to switch the year?
625    var yearChanged = !self.month.isSame( moment(self.month).subtract(1, 'months'), 'year');
626    self.month.subtract(1, 'months');
627
628    self.render();
629
630    if(self.options.clickEvents.previousMonth) {
631      self.options.clickEvents.previousMonth.apply( self, [moment(self.month)] );
632    }
633    if(self.options.clickEvents.onMonthChange) {
634      self.options.clickEvents.onMonthChange.apply( self, [moment(self.month)] );
635    }
636    if(yearChanged) {
637      if(self.options.clickEvents.onYearChange) {
638        self.options.clickEvents.onYearChange.apply( self, [moment(self.month)] );
639      }
640    }
641  };
642
643  Clndr.prototype.forwardActionWithContext = function(self) {
644    // before we do anything, check if there is an inactive class on the month control.
645    // if it does, we want to return and take no action.
646    if(self.element.find('.' + self.options.targets.nextButton).hasClass('inactive')) {
647      return;
648    }
649
650    // is adding one month going to switch the year?
651    var yearChanged = !self.month.isSame( moment(self.month).add(1, 'months'), 'year');
652    self.month.add(1, 'months');
653
654    self.render();
655
656    if(self.options.clickEvents.nextMonth) {
657      self.options.clickEvents.nextMonth.apply(self, [moment(self.month)]);
658    }
659    if(self.options.clickEvents.onMonthChange) {
660      self.options.clickEvents.onMonthChange.apply(self, [moment(self.month)]);
661    }
662    if(yearChanged) {
663      if(self.options.clickEvents.onYearChange) {
664        self.options.clickEvents.onYearChange.apply( self, [moment(self.month)] );
665      }
666    }
667  };
668
669  Clndr.prototype.todayAction = function(event) {
670    var self = event.data.context;
671
672    // did we switch months when the today button was hit?
673    var monthChanged = !self.month.isSame(moment(), 'month');
674    var yearChanged = !self.month.isSame(moment(), 'year');
675
676    self.month = moment().startOf('month');
677
678    // fire the today event handler regardless of whether the month changed.
679    if(self.options.clickEvents.today) {
680      self.options.clickEvents.today.apply( self, [moment(self.month)] );
681    }
682
683    if(monthChanged) {
684      // no need to re-render if we didn't change months.
685      self.render();
686
687      self.month = moment();
688      // fire the onMonthChange callback
689      if(self.options.clickEvents.onMonthChange) {
690        self.options.clickEvents.onMonthChange.apply( self, [moment(self.month)] );
691      }
692      // maybe fire the onYearChange callback?
693      if(yearChanged) {
694        if(self.options.clickEvents.onYearChange) {
695          self.options.clickEvents.onYearChange.apply( self, [moment(self.month)] );
696        }
697      }
698    }
699  };
700
701  Clndr.prototype.nextYearAction = function(event) {
702    var self = event.data.context;
703    // before we do anything, check if there is an inactive class on the month control.
704    // if it does, we want to return and take no action.
705    if(self.element.find('.' + self.options.targets.nextYearButton).hasClass('inactive')) {
706      return;
707    }
708
709    self.month.add(1, 'years');
710    self.render();
711
712    if(self.options.clickEvents.nextYear) {
713      self.options.clickEvents.nextYear.apply( self, [moment(self.month)] );
714    }
715    if(self.options.clickEvents.onMonthChange) {
716      self.options.clickEvents.onMonthChange.apply( self, [moment(self.month)] );
717    }
718    if(self.options.clickEvents.onYearChange) {
719      self.options.clickEvents.onYearChange.apply( self, [moment(self.month)] );
720    }
721  };
722
723  Clndr.prototype.previousYearAction = function(event) {
724    var self = event.data.context;
725    // before we do anything, check if there is an inactive class on the month control.
726    // if it does, we want to return and take no action.
727    if(self.element.find('.' + self.options.targets.previousYear).hasClass('inactive')) {
728      return;
729    }
730
731    self.month.subtract(1, 'years');
732    self.render();
733
734    if(self.options.clickEvents.previousYear) {
735      self.options.clickEvents.previousYear.apply( self, [moment(self.month)] );
736    }
737    if(self.options.clickEvents.onMonthChange) {
738      self.options.clickEvents.onMonthChange.apply( self, [moment(self.month)] );
739    }
740    if(self.options.clickEvents.onYearChange) {
741      self.options.clickEvents.onYearChange.apply( self, [moment(self.month)] );
742    }
743  };
744
745  Clndr.prototype.forward = function(options) {
746    this.month.add(1, 'months');
747    this.render();
748    if(options && options.withCallbacks) {
749      if(this.options.clickEvents.onMonthChange) {
750        this.options.clickEvents.onMonthChange.apply( this, [moment(this.month)] );
751      }
752
753      // We entered a new year
754      if (this.month.month() === 0 && this.options.clickEvents.onYearChange) {
755        this.options.clickEvents.onYearChange.apply( this, [moment(this.month)] );
756      }
757    }
758
759    return this;
760  }
761
762  Clndr.prototype.back = function(options) {
763    this.month.subtract(1, 'months');
764    this.render();
765    if(options && options.withCallbacks) {
766      if(this.options.clickEvents.onMonthChange) {
767        this.options.clickEvents.onMonthChange.apply( this, [moment(this.month)] );
768      }
769
770      // We went all the way back to previous year
771      if (this.month.month() === 11 && this.options.clickEvents.onYearChange) {
772        this.options.clickEvents.onYearChange.apply( this, [moment(this.month)] );
773      }
774    }
775
776    return this;
777  }
778
779  // alternate names for convenience
780  Clndr.prototype.next = function(options) {
781    this.forward(options);
782    return this;
783  }
784
785  Clndr.prototype.previous = function(options) {
786    this.back(options);
787    return this;
788  }
789
790  Clndr.prototype.setMonth = function(newMonth, options) {
791    // accepts 0 - 11 or a full/partial month name e.g. "Jan", "February", "Mar"
792    this.month.month(newMonth);
793    this.render();
794    if(options && options.withCallbacks) {
795      if(this.options.clickEvents.onMonthChange) {
796        this.options.clickEvents.onMonthChange.apply( this, [moment(this.month)] );
797      }
798    }
799    return this;
800  }
801
802  Clndr.prototype.nextYear = function(options) {
803    this.month.add(1, 'year');
804    this.render();
805    if(options && options.withCallbacks) {
806      if(this.options.clickEvents.onYearChange) {
807        this.options.clickEvents.onYearChange.apply( this, [moment(this.month)] );
808      }
809    }
810    return this;
811  }
812
813  Clndr.prototype.previousYear = function(options) {
814    this.month.subtract(1, 'year');
815    this.render();
816    if(options && options.withCallbacks) {
817      if(this.options.clickEvents.onYearChange) {
818        this.options.clickEvents.onYearChange.apply( this, [moment(this.month)] );
819      }
820    }
821    return this;
822  }
823
824  Clndr.prototype.setYear = function(newYear, options) {
825    this.month.year(newYear);
826    this.render();
827    if(options && options.withCallbacks) {
828      if(this.options.clickEvents.onYearChange) {
829        this.options.clickEvents.onYearChange.apply( this, [moment(this.month)] );
830      }
831    }
832    return this;
833  }
834
835  Clndr.prototype.setEvents = function(events) {
836    // go through each event and add a moment object
837    if(this.options.multiDayEvents) {
838      this.options.events = this.addMultiDayMomentObjectsToEvents(events);
839    } else {
840      this.options.events = this.addMomentObjectToEvents(events);
841    }
842
843    this.render();
844    return this;
845  };
846
847  Clndr.prototype.addEvents = function(events) {
848    // go through each event and add a moment object
849    if(this.options.multiDayEvents) {
850      this.options.events = $.merge(this.options.events, this.addMultiDayMomentObjectsToEvents(events));
851    } else {
852      this.options.events = $.merge(this.options.events, this.addMomentObjectToEvents(events));
853    }
854
855    this.render();
856    return this;
857  };
858
859  Clndr.prototype.removeEvents = function(matchingFunction) {
860    for (var i = this.options.events.length-1; i >= 0; i--) {
861      if(matchingFunction(this.options.events[i]) == true) {
862        this.options.events.splice(i, 1);
863      }
864    }
865
866    this.render();
867    return this;
868  };
869
870  Clndr.prototype.addMomentObjectToEvents = function(events) {
871    var self = this;
872    var i = 0, l = events.length;
873    for(i; i < l; i++) {
874      // stuff a _clndrDateObject in each event, which really, REALLY should not be
875      // overriding any existing object... Man that would be weird.
876      events[i]._clndrDateObject = moment( events[i][self.options.dateParameter] );
877    }
878    return events;
879  }
880
881  Clndr.prototype.addMultiDayMomentObjectsToEvents = function(events) {
882    var self = this;
883    var i = 0, l = events.length;
884    for(i; i < l; i++) {
885      events[i]._clndrStartDateObject = moment( events[i][self.options.multiDayEvents.startDate] );
886      events[i]._clndrEndDateObject = moment( events[i][self.options.multiDayEvents.endDate] );
887    }
888    return events;
889  }
890
891  Clndr.prototype.calendarDay = function(options) {
892    var defaults = { day: "", classes: this.options.targets.empty, events: [], date: null };
893    return $.extend({}, defaults, options);
894  }
895
896  $.fn.clndr = function(options) {
897    if(this.length === 1) {
898      if(!this.data('plugin_clndr')) {
899        var clndr_instance = new Clndr(this, options);
900        this.data('plugin_clndr', clndr_instance);
901        return clndr_instance;
902      }
903    } else if(this.length > 1) {
904      throw new Error("CLNDR does not support multiple elements yet. Make sure your clndr selector returns only one element.");
905    }
906  }
907
908}));

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.