clndr-rails 1.2.5.2 → 1.2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/lib/clndr-rails/version.rb +1 -1
- data/vendor/assets/javascripts/clndr-rails/clndr.min.js +908 -0
- data/vendor/assets/javascripts/clndr-rails/index.js +4 -0
- data/vendor/assets/stylesheets/clndr-rails.css +391 -0
- metadata +5 -4
- data/test/dummy/tmp/pids/server.pid +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd9df4c964a423fd974e9f926fcac21d55bece63
|
4
|
+
data.tar.gz: 83254c1feb66ab9e385be15e60e763f4b88df9d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b6f7921d44c3571fa334f2bcc409ff9b8e1c3efd51bf32197a5d8737107438d09d7cfefce5610fa45c9443c27658987a2d383e24458fcff288d1d9e89917423
|
7
|
+
data.tar.gz: 3755c0a8b93bfd0a15483508e04737bde9e2404b0059fe70cbb37ef787d15bc470005d1b6c0e00ea6bd465a7239a2218f733ab9fea42992f19dba4499d4d00ba
|
data/MIT-LICENSE
CHANGED
data/lib/clndr-rails/version.rb
CHANGED
@@ -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
|
+
}));
|