clndr-rails 1.2.5.2 → 1.2.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bcfb9af88681840cc8a39d9d5f58419cc23c3cb7
4
- data.tar.gz: 6d3638267750032a543e264c8a92f82692007fa1
3
+ metadata.gz: dd9df4c964a423fd974e9f926fcac21d55bece63
4
+ data.tar.gz: 83254c1feb66ab9e385be15e60e763f4b88df9d5
5
5
  SHA512:
6
- metadata.gz: e119b9ce2449950f9ca131068d076a9070db30459a99ca5ffd5efe73dbe58ee22a394f5faf9068c879bb8951722f4deedaad06c67d3a6798bda9d0b504e638be
7
- data.tar.gz: 906b7581355652dbcf9b1e38c20cee95969a09bb659076e51992485811030cd0b4275f6d5ae87a06864d3f6294d30b073fa4db6318706305fb62d8a9354140fa
6
+ metadata.gz: 4b6f7921d44c3571fa334f2bcc409ff9b8e1c3efd51bf32197a5d8737107438d09d7cfefce5610fa45c9443c27658987a2d383e24458fcff288d1d9e89917423
7
+ data.tar.gz: 3755c0a8b93bfd0a15483508e04737bde9e2404b0059fe70cbb37ef787d15bc470005d1b6c0e00ea6bd465a7239a2218f733ab9fea42992f19dba4499d4d00ba
@@ -1,4 +1,4 @@
1
- Copyright 2014 YOURNAME
1
+ Copyright 2014 Ilya Bondarenko
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -1,5 +1,5 @@
1
1
  class Clndr
2
2
  module Rails
3
- VERSION = "1.2.5.2"
3
+ VERSION = "1.2.5.3"
4
4
  end
5
5
  end
@@ -0,0 +1,908 @@
1
+ /*
2
+ * ~ CLNDR v1.2.5 ~
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('dd').charAt(0) );
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([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([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
+ }));