kalendae_assets 0.0.1 → 0.1.0

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.
@@ -0,0 +1,1441 @@
1
+ /********************************************************************
2
+ * Kalendae, a framework agnostic javascript date picker *
3
+ * Copyright(c) 2012 Jarvis Badgley (chipersoft@gmail.com) *
4
+ * http://github.com/ChiperSoft/Kalendae *
5
+ * Version 0.1 *
6
+ ********************************************************************/
7
+
8
+ (function (undefined) {
9
+
10
+ var today;
11
+
12
+ var Kalendae = function (targetElement, options) {
13
+ //if the first argument isn't an element and isn't a string, assume that it is the options object
14
+ if (!(targetElement instanceof Element || typeof targetElement === 'string')) options = targetElement;
15
+
16
+ var self = this,
17
+ classes = self.classes,
18
+ opts = self.settings = util.merge(self.defaults, {attachTo:targetElement}, options || {}),
19
+ $container = self.container = util.make('div', {'class':classes.container}),
20
+ calendars = self.calendars = [],
21
+ startDay = moment().day(opts.weekStart),
22
+ vsd,
23
+ columnHeaders = [],
24
+ $cal,
25
+ $title,
26
+ $caption,
27
+ $header,
28
+ $days, dayNodes = [],
29
+ $span,
30
+ i = 0,
31
+ j = opts.months;
32
+
33
+ //generate the column headers (Su, Mo, Tu, etc)
34
+ i = 7;
35
+ while (i--) {
36
+ columnHeaders.push( startDay.format('ddd').substr(0,opts.columnHeaderLength) );
37
+ startDay.add('days',1);
38
+ }
39
+
40
+ //setup publish/subscribe and apply any subscriptions passed in settings
41
+ MinPubSub(self);
42
+ if (typeof opts.subscribe === 'object') {
43
+ for (i in opts.subscribe) if (opts.subscribe.hasOwnProperty(i)) {
44
+ self.subscribe(i, opts.subscribe[i]);
45
+ }
46
+ }
47
+
48
+ //process default selected dates
49
+ self._sel = [];
50
+ if (!!opts.selected) self.setSelected(opts.selected, false);
51
+
52
+ //set the view month
53
+ if (!!opts.viewStartDate) {
54
+ vsd = moment(opts.viewStartDate, opts.format);
55
+ } else if (self._sel.length > 0) {
56
+ vsd = moment(self._sel[0]);
57
+ } else {
58
+ vsd = moment();
59
+ }
60
+ self.viewStartDate = vsd.date(1);
61
+
62
+
63
+ if (typeof opts.blackout === 'function') {
64
+ self.blackout = opts.blackout;
65
+ } else if (!!opts.blackout) {
66
+ var bdates = parseDates(opts.blackout, opts.parseSplitDelimiter);
67
+ self.blackout = function (input) {
68
+ input = moment(input).hours(0).minutes(0).seconds(0).milliseconds(0).valueOf();
69
+ if (input < 1 || !self._sel || self._sel.length < 1) return false;
70
+ var i = bdates.length;
71
+ while (i--) if (bdates[i].valueOf() === input) return true;
72
+ return false;
73
+ }
74
+ } else {
75
+ self.blackout = function () {return false;}
76
+ }
77
+
78
+
79
+ self.direction = self.directions[opts.direction] ? self.directions[opts.direction] : self.directions['any'];
80
+
81
+
82
+ //for the total months setting, generate N calendar views and add them to the container
83
+ j = Math.max(opts.months,1);
84
+ while (j--) {
85
+ $cal = util.make('div', {'class':classes.calendar}, $container);
86
+
87
+ $cal.setAttribute('data-cal-index', j);
88
+ if (opts.months > 1) {
89
+ if (j == Math.max(opts.months-1,1)) util.addClassName($cal, classes.monthFirst);
90
+ else if (j === 0) util.addClassName($cal, classes.monthLast);
91
+ else util.addClassName($cal, classes.monthMiddle);
92
+ }
93
+
94
+ //title bar
95
+ $title = util.make('div', {'class':classes.title}, $cal);
96
+ util.make('a', {'class':classes.previous}, $title); //previous button
97
+ util.make('a', {'class':classes.next}, $title); //next button
98
+ $caption = util.make('span', {'class':classes.caption}, $title); //title caption
99
+
100
+ //column headers
101
+ $header = util.make('div', {'class':classes.header}, $cal);
102
+ i = 0;
103
+ do {
104
+ $span = util.make('span', {}, $header);
105
+ $span.innerHTML = columnHeaders[i];
106
+ } while (++i < 7)
107
+
108
+ //individual day cells
109
+ $days = util.make('div', {'class':classes.days}, $cal);
110
+ i = 0;
111
+ dayNodes = [];
112
+ while (i++ < 42) {
113
+ dayNodes.push(util.make('span', {}, $days));
114
+ }
115
+
116
+ //store each calendar view for easy redrawing
117
+ calendars.push({
118
+ caption:$caption,
119
+ days:dayNodes
120
+ });
121
+
122
+ if (j) util.make('div', {'class':classes.monthSeparator}, $container);
123
+ }
124
+
125
+ self.draw();
126
+
127
+ util.addEvent($container, 'mousedown', function (event, target) {
128
+ var clickedDate;
129
+ if (util.hasClassName(target, classes.next)) {
130
+ //NEXT MONTH BUTTON
131
+ if (self.publish('view-changed', self, ['next']) !== false) {
132
+ self.viewStartDate.add('months',1);
133
+ self.draw();
134
+ }
135
+ return false;
136
+
137
+ } else if (util.hasClassName(target, classes.previous)) {
138
+ //PREVIOUS MONTH BUTTON
139
+ if (self.publish('view-changed', self, ['previous']) !== false) {
140
+ self.viewStartDate.subtract('months',1);
141
+ self.draw();
142
+ }
143
+ return false;
144
+
145
+
146
+ } else if (util.hasClassName(target.parentNode, classes.days) && util.hasClassName(target, classes.dayActive) && (clickedDate = target.getAttribute('data-date'))) {
147
+ //DAY CLICK
148
+ clickedDate = moment(clickedDate, opts.dayAttributeFormat);
149
+ if (self.publish('date-clicked', self, [clickedDate]) !== false) {
150
+
151
+ switch (opts.mode) {
152
+ case 'multiple':
153
+ if (!self.addSelected(clickedDate)) self.removeSelected(clickedDate);
154
+ break;
155
+ case 'range':
156
+ self.addSelected(clickedDate);
157
+ break;
158
+ case 'single':
159
+ /* falls through */
160
+ default:
161
+ self.addSelected(clickedDate);
162
+ break;
163
+ }
164
+
165
+ }
166
+ return false;
167
+
168
+ }
169
+ return false;
170
+ });
171
+
172
+
173
+ if (!!(opts.attachTo = util.$(opts.attachTo))) {
174
+ opts.attachTo.appendChild($container);
175
+ }
176
+
177
+ };
178
+
179
+ Kalendae.prototype = {
180
+ defaults : {
181
+ attachTo: null, /* the element to attach the root container to. can be string or DOMElement */
182
+ months: 1, /* total number of months to display side by side */
183
+ weekStart: 0, /* day to use for the start of the week. 0 is Sunday */
184
+ direction: 'any', /* past, today-past, any, today-future, future */
185
+ viewStartDate: null, /* date in the month to display. When multiple months, this is the left most */
186
+ blackout: null, /* array of dates, or function to be passed a date */
187
+ selected: null, /* dates already selected. can be string, date, or array of strings or dates. */
188
+ mode: 'single', /* single, multiple, range */
189
+ format: null, /* string used for parsing dates. */
190
+ subscribe: null, /* object containing events to subscribe to */
191
+
192
+ columnHeaderLength: 2, /* number of characters to show in the column headers */
193
+ titleFormat: 'MMMM, YYYY', /* format mask for month titles. See momentjs.com for rules */
194
+ dayNumberFormat: 'D', /* format mask for individual days */
195
+ dayAttributeFormat: 'YYYY-MM-DD', /* format mask for the data-date attribute set on every span */
196
+ parseSplitDelimiter: /,\s*|\s*-\s*/, /* regex to use for splitting multiple dates from a passed string */
197
+ rangeDelimiter: ' - ', /* string to use between dates when outputting in range mode */
198
+ multipleDelimiter: ', ', /* string to use between dates when outputting in multiple mode */
199
+
200
+ dateClassMap: {}
201
+ },
202
+ classes : {
203
+ container :'kalendae',
204
+ calendar :'k-calendar',
205
+ monthFirst :'k-first-month',
206
+ monthMiddle :'k-middle-month',
207
+ monthLast :'k-last-month',
208
+ title :'k-title',
209
+ previous :'k-previous',
210
+ next :'k-next',
211
+ caption :'k-caption',
212
+ header :'k-header',
213
+ days :'k-days',
214
+ dayOutOfMonth :'k-out-of-month',
215
+ dayActive :'k-active',
216
+ daySelected :'k-selected',
217
+ dayInRange :'k-range',
218
+ dayToday :'k-today',
219
+ monthSeparator :'k-separator'
220
+ },
221
+
222
+ directions: {
223
+ 'past' :function (date) {return moment(date).valueOf() >= today.valueOf();},
224
+ 'today-past' :function (date) {return moment(date).valueOf() > today.valueOf();},
225
+ 'any' :function (date) {return false;},
226
+ 'today-future' :function (date) {return moment(date).valueOf() < today.valueOf();},
227
+ 'future' :function (date) {return moment(date).valueOf() <= today.valueOf();}
228
+ },
229
+
230
+ getSelectedAsDates : function () {
231
+ var out = [];
232
+ var i=0, c = this._sel.length;
233
+ for (;i<c;i++) {
234
+ out.push(this._sel[i].nativeDate());
235
+ }
236
+ return out;
237
+ },
238
+
239
+ getSelectedAsText : function (format) {
240
+ var out = [];
241
+ var i=0, c = this._sel.length;
242
+ for (;i<c;i++) {
243
+ out.push(this._sel[i].format(format || this.settings.format || 'YYYY-MM-DD'))
244
+ }
245
+ return out;
246
+ },
247
+
248
+ getSelectedRaw : function () {
249
+ var out = [];
250
+ var i=0, c = this._sel.length;
251
+ for (;i<c;i++) {
252
+ out.push(moment(this._sel[i]))
253
+ }
254
+ return out;
255
+ },
256
+
257
+ getSelected : function (format) {
258
+ var sel = this.getSelectedAsText(format);
259
+ switch (this.settings.mode) {
260
+ case 'range':
261
+ sel.splice(2); //shouldn't be more than two, but lets just make sure.
262
+ return sel.join(this.settings.rangeDelimiter);
263
+
264
+ case 'multiple':
265
+ return sel.join(this.settings.multipleDelimiter);
266
+
267
+ case 'single':
268
+ /* falls through */
269
+ default:
270
+ return sel[0];
271
+ }
272
+ },
273
+
274
+ isSelected : function (input) {
275
+ input = moment(input).hours(0).minutes(0).seconds(0).milliseconds(0).valueOf();
276
+ if (input < 1 || !this._sel || this._sel.length < 1) return false;
277
+
278
+ switch (this.settings.mode) {
279
+ case 'range':
280
+ var a = this._sel[0] ? this._sel[0].valueOf() : 0,
281
+ b = this._sel[1] ? this._sel[1].valueOf() : 0;
282
+
283
+ if (a === input || b === input) return 1;
284
+ if (!a || !b) return 0;
285
+
286
+ if ((input > a && input < b) || (a<b && input < a && input > b)) return -1;
287
+ return false;
288
+
289
+ case 'multiple':
290
+ var i = this._sel.length;
291
+ while (i--) {
292
+ if (this._sel[i].valueOf() === input) {
293
+ return true;
294
+ }
295
+ }
296
+ return false;
297
+
298
+
299
+ case 'single':
300
+ /* falls through */
301
+ default:
302
+ return (this._sel[0] && (this._sel[0].valueOf() === input));
303
+ }
304
+
305
+ return false;
306
+ },
307
+
308
+ setSelected : function (input, draw) {
309
+ this._sel = parseDates(input, this.settings.parseSplitDelimiter, this.settings.format);
310
+ this._sel.sort(function (a,b) {return a.valueOf() - b.valueOf();});
311
+
312
+ if (draw !== false) this.draw();
313
+ },
314
+
315
+ addSelected : function (date, draw) {
316
+ date = moment(date).hours(0).minutes(0).seconds(0).milliseconds(0);
317
+ switch (this.settings.mode) {
318
+ case 'multiple':
319
+ if (!this.isSelected(date)) this._sel.push(date);
320
+ else return false;
321
+ break;
322
+ case 'range':
323
+
324
+ if (this._sel.length !== 1) this._sel = [date];
325
+ else {
326
+ if (date.valueOf() > this._sel[0].valueOf()) this._sel[1] = date;
327
+ else this._sel = [date, this._sel[0]];
328
+ }
329
+ break;
330
+ case 'single':
331
+ /* falls through */
332
+ default:
333
+ this._sel = [date];
334
+ break;
335
+ }
336
+ this._sel.sort(function (a,b) {return a.valueOf() - b.valueOf();});
337
+ this.publish('change', this);
338
+ if (draw !== false) this.draw();
339
+ return true;
340
+ },
341
+
342
+ removeSelected : function (date, draw) {
343
+ date = moment(date).hours(0).minutes(0).seconds(0).milliseconds(0).valueOf();
344
+ var i = this._sel.length;
345
+ while (i--) {
346
+ if (this._sel[i].valueOf() === date) {
347
+ this._sel.splice(i,1);
348
+ this.publish('change', this);
349
+ if (draw !== false) this.draw();
350
+ return true;
351
+ }
352
+ }
353
+ return false;
354
+ },
355
+
356
+ draw : function draw() {
357
+ // return;
358
+ var month = moment(this.viewStartDate),
359
+ day,
360
+ classes = this.classes,
361
+ cal,
362
+ $span,
363
+ klass,
364
+ i=0, c,
365
+ j=0, k,
366
+ s,
367
+ dateString,
368
+ opts = this.settings;
369
+
370
+ c = this.calendars.length;
371
+
372
+ var viewDelta = ({
373
+ 'past' : c-1,
374
+ 'today-past' : c-1,
375
+ 'any' : c>2?Math.floor(c/2):0,
376
+ 'today-future' : 0,
377
+ 'future' : 0
378
+ })[this.settings.direction];
379
+
380
+ if (viewDelta) month = month.subtract({M:viewDelta});
381
+
382
+ do {
383
+ day = moment(month).date(1);
384
+ day.day( day.day() < this.settings.weekStart ? this.settings.weekStart-7 : this.settings.weekStart);
385
+ //if the first day of the month is less than our week start, back up a week
386
+
387
+ cal = this.calendars[i];
388
+ cal.caption.innerHTML = month.format(this.settings.titleFormat);
389
+ j = 0;
390
+ do {
391
+ $span = cal.days[j];
392
+
393
+ klass = [];
394
+
395
+ s = this.isSelected(day);
396
+
397
+ if (s) klass.push(({'-1':classes.dayInRange,'1':classes.daySelected, 'true':classes.daySelected})[s]);
398
+
399
+ if (day.month() != month.month()) klass.push(classes.dayOutOfMonth);
400
+ else if (!(this.blackout(day) || this.direction(day)) || s>0) klass.push(classes.dayActive);
401
+
402
+ if (Math.floor(today.diff(day, 'days', true)) === 0) klass.push(classes.dayToday);
403
+
404
+ dateString = day.format(this.settings.dayAttributeFormat);
405
+ if (opts.dateClassMap[dateString]) klass.push(opts.dateClassMap[dateString]);
406
+
407
+ $span.innerHTML = day.format(opts.dayNumberFormat);
408
+ $span.className = klass.join(' ');
409
+ $span.setAttribute('data-date', dateString);
410
+
411
+
412
+ day.add('days',1);
413
+ } while (++j < 42);
414
+ month.add('months',1);
415
+ } while (++i < c);
416
+
417
+ }
418
+ }
419
+
420
+ var parseDates = function (input, delimiter, format) {
421
+ var output = [];
422
+
423
+ if (typeof input === 'string') {
424
+ input = input.split(delimiter);
425
+ } else if (!util.isArray(input)) {
426
+ input = [input];
427
+ }
428
+
429
+ c = input.length;
430
+ i = 0;
431
+ do {
432
+ if (input[i]) output.push( moment(input[i], format).hours(0).minutes(0).seconds(0).milliseconds(0) );
433
+ } while (++i < c);
434
+
435
+ return output;
436
+ }
437
+
438
+
439
+
440
+ window.Kalendae = Kalendae;
441
+
442
+ var util = Kalendae.util = {
443
+ // ELEMENT FUNCTIONS
444
+
445
+ $: function (elem) {
446
+ return (typeof elem == 'string') ? document.getElementById(elem) : elem;
447
+ },
448
+
449
+ $$: function (selector) {
450
+ return document.querySelectorAll(selector);
451
+ },
452
+
453
+ make: function (tagName, attributes, attach) {
454
+ var k, e = document.createElement(tagName);
455
+ if (!!attributes) for (k in attributes) if (attributes.hasOwnProperty(k)) e.setAttribute(k, attributes[k]);
456
+ if (!!attach) attach.appendChild(e);
457
+ return e;
458
+ },
459
+
460
+ // Returns true if the DOM element is visible, false if it's hidden.
461
+ // Checks if display is anything other than none.
462
+ isVisible: function (elem) {
463
+ // shamelessly copied from jQuery
464
+ return elem.offsetWidth > 0 || elem.offsetHeight > 0;
465
+ },
466
+
467
+ domReady:function (f){/in/.test(document.readyState) ? setTimeout(function() {util.domReady(f);},9) : f()},
468
+
469
+ // Adds a listener callback to a DOM element which is fired on a specified
470
+ // event. Callback is sent the event object and the element that triggered the event
471
+ addEvent: function (elem, eventName, callback) {
472
+ var listener = function (event) {
473
+ event = event || window.event;
474
+ var target = event.target || event.srcElement;
475
+ var block = callback.apply(elem, [event, target]);
476
+ if (block === false) {
477
+ if (!!event.preventDefault) event.preventDefault();
478
+ else {
479
+ event.returnValue = false;
480
+ event.cancelBubble = true;
481
+ }
482
+ }
483
+ return block;
484
+ };
485
+ if (elem.attachEvent) { // IE only. The "on" is mandatory.
486
+ elem.attachEvent("on" + eventName, listener);
487
+ } else { // Other browsers.
488
+ elem.addEventListener(eventName, listener, false);
489
+ }
490
+ return listener;
491
+ },
492
+
493
+ // Removes a listener callback from a DOM element which is fired on a specified
494
+ // event.
495
+ removeEvent: function (elem, event, listener) {
496
+ if (elem.detachEvent) { // IE only. The "on" is mandatory.
497
+ elem.detachEvent("on" + event, listener);
498
+ } else { // Other browsers.
499
+ elem.removeEventListener(event, listener, false);
500
+ }
501
+ },
502
+
503
+ hasClassName: function(elem, className) { //copied and modified from Prototype.js
504
+ if (!(elem = util.$(elem))) return false;
505
+ var eClassName = elem.className;
506
+ return (eClassName.length > 0 && (eClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(eClassName)));
507
+ },
508
+
509
+ addClassName: function(elem, className) { //copied and modified from Prototype.js
510
+ if (!(elem = util.$(elem))) return;
511
+ if (!util.hasClassName(elem, className)) elem.className += (elem.className ? ' ' : '') + className;
512
+ },
513
+
514
+ removeClassName: function(elem, className) { //copied and modified from Prototype.js
515
+ if (!(elem = util.$(elem))) return;
516
+ elem.className = util.trimString(elem.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' '));
517
+ },
518
+
519
+ getPosition: function (elem, isInner) {
520
+ var x = elem.offsetLeft,
521
+ y = elem.offsetTop,
522
+ r = {};
523
+
524
+ if (!isInner) {
525
+ while ((elem = elem.offsetParent)) {
526
+ x += elem.offsetLeft;
527
+ y += elem.offsetTop;
528
+ }
529
+ }
530
+
531
+ r[0] = r.left = x;
532
+ r[1] = r.top = y;
533
+ return r;
534
+ },
535
+
536
+ getHeight: function (elem) {
537
+ return elem.offsetHeight || elem.scrollHeight;
538
+ },
539
+
540
+ getWidth: function (elem) {
541
+ return elem.offsetWidth || elem.scrollWidth;
542
+ },
543
+
544
+
545
+ // TEXT FUNCTIONS
546
+
547
+ trimString: function (input) {
548
+ return input.replace(/^\s+/, '').replace(/\s+$/, '');
549
+ },
550
+
551
+
552
+ // OBJECT FUNCTIONS
553
+
554
+ merge: function () {
555
+ /* Combines multiple objects into one.
556
+ * Syntax: util.extend([true], object1, object2, ... objectN)
557
+ * If first argument is true, function will merge recursively.
558
+ */
559
+
560
+ var deep = (arguments[0]===true),
561
+ d = {},
562
+ i = deep?1:0;
563
+
564
+ var _c = function (a, b) {
565
+ if (typeof b !== 'object') return;
566
+ for (var k in b) if (b.hasOwnProperty(k)) {
567
+ //if property is an object or array, merge the contents instead of overwriting, if extend() was called as such
568
+ if (deep && typeof a[k] === 'object' && typeof b[k] === 'object') _update(a[k], b[k]);
569
+ else a[k] = b[k];
570
+ }
571
+ return a;
572
+ }
573
+
574
+ for (; i < arguments.length; i++) {
575
+ _c(d, arguments[i]);
576
+ }
577
+ return d;
578
+ },
579
+
580
+ isArray: function (array) {
581
+ return !(
582
+ !array ||
583
+ (!array.length || array.length === 0) ||
584
+ typeof array !== 'object' ||
585
+ !array.constructor ||
586
+ array.nodeType ||
587
+ array.item
588
+ );
589
+ }
590
+ };
591
+
592
+
593
+ //auto-initializaiton code
594
+ Kalendae.util.domReady(function () {
595
+ var els = util.$$('.auto-kal'),
596
+ i = els.length,
597
+ e;
598
+
599
+ while (i--) {
600
+ e = els[i];
601
+ if (e.tagName === 'INPUT') {
602
+ //if element is an input, bind a popup calendar to the input.
603
+ new Kalendae.Input(e);
604
+ } else {
605
+ //otherwise, insert a flat calendar into the element.
606
+ new Kalendae({attachTo:e});
607
+ }
608
+
609
+ }
610
+ });
611
+
612
+ Kalendae.Input = function (targetElement, options) {
613
+ var $input = this.input = util.$(targetElement),
614
+ overwriteInput;
615
+
616
+ if (!$input || $input.tagName !== 'INPUT') throw "First argument for Kalendae.Input must be an <input> element or a valid element id.";
617
+
618
+ var self = this,
619
+ classes = self.classes
620
+ opts = self.settings = util.merge(self.defaults, options);
621
+
622
+ //force attachment to the body
623
+ opts.attachTo = window.document.body;
624
+
625
+ //if no override provided, use the input's contents
626
+ if (!opts.selected) opts.selected = $input.value;
627
+ else overwriteInput = true;
628
+
629
+ //call our parent constructor
630
+ Kalendae.call(self, opts);
631
+
632
+ if (overwriteInput) $input.value = self.getSelected();
633
+
634
+ var $container = self.container,
635
+ noclose = false;
636
+
637
+ $container.style.display = 'none';
638
+ util.addClassName($container, classes.positioned);
639
+
640
+ util.addEvent($container, 'mousedown', function (event, target) {
641
+ noclose = true; //IE8 doesn't obey event blocking when it comes to focusing, so we have to do this shit.
642
+ });
643
+ util.addEvent(window.document, 'mousedown', function (event, target) {
644
+ noclose = false;
645
+ });
646
+
647
+ util.addEvent($input, 'focus', function () {
648
+ self.setSelected(this.value);
649
+ self.show();
650
+ });
651
+
652
+ util.addEvent($input, 'blur', function () {
653
+ if (noclose) {
654
+ noclose = false;
655
+ $input.focus();
656
+ }
657
+ else self.hide();
658
+ });
659
+ util.addEvent($input, 'keyup', function (event) {
660
+ self.setSelected(this.value);
661
+ });
662
+
663
+ self.subscribe('change', function () {
664
+ $input.value = self.getSelected();
665
+ });
666
+
667
+ };
668
+
669
+ Kalendae.Input.prototype = util.merge(Kalendae.prototype, {
670
+ defaults : util.merge(Kalendae.prototype.defaults, {
671
+ format: 'MM/DD/YYYY',
672
+ side: 'bottom',
673
+ offsetLeft: 0,
674
+ offsetTop: 0
675
+ }),
676
+ classes : util.merge(Kalendae.prototype.classes, {
677
+ positioned : 'k-floating'
678
+
679
+ }),
680
+
681
+ show : function () {
682
+ var $container = this.container,
683
+ style = $container.style,
684
+ $input = this.input,
685
+ pos = util.getPosition($input);
686
+
687
+ style.display = '';
688
+ switch (opts.side) {
689
+ case 'left':
690
+ style.left = (pos.left - util.getWidth($container) + this.settings.offsetLeft) + 'px';
691
+ style.top = (pos.top + this.settings.offsetTop) + 'px';
692
+ break;
693
+ case 'right':
694
+ style.left = (pos.left + util.getWidth($input)) + 'px';
695
+ style.top = (pos.top + this.settings.offsetTop) + 'px';
696
+ break;
697
+ case 'top':
698
+ style.left = (pos.left + this.settings.offsetLeft) + 'px';
699
+ style.top = (pos.top - util.getHeight($container) + this.settings.offsetTop) + 'px';
700
+ break;
701
+ case 'bottom':
702
+ /* falls through */
703
+ default:
704
+ style.left = (pos.left + this.settings.offsetLeft) + 'px';
705
+ style.top = (pos.top + util.getHeight($input) + this.settings.offsetTop) + 'px';
706
+ break;
707
+ }
708
+
709
+ },
710
+
711
+ hide : function () {
712
+ this.container.style.display = 'none';
713
+ }
714
+
715
+ });
716
+
717
+
718
+ /*!
719
+ * MinPubSub, modified for use on Kalendae
720
+ * Copyright(c) 2011 Daniel Lamb <daniellmb.com>
721
+ * https://github.com/daniellmb/MinPubSub
722
+ * MIT Licensed
723
+ */
724
+
725
+ var MinPubSub = function(d){
726
+
727
+ if (!d) d = this;
728
+
729
+ // the topic/subscription hash
730
+ var cache = d.c_ || {}; //check for "c_" cache for unit testing
731
+
732
+ d.publish = function(/* String */ topic, /* Object */ target, /* Array? */ args){
733
+ // summary:
734
+ // Publish some data on a named topic.
735
+ // topic: String
736
+ // The channel to publish on
737
+ // args: Array?
738
+ // The data to publish. Each array item is converted into an ordered
739
+ // arguments on the subscribed functions.
740
+ //
741
+ // example:
742
+ // Publish stuff on '/some/topic'. Anything subscribed will be called
743
+ // with a function signature like: function(a,b,c){ ... }
744
+ //
745
+ // publish("/some/topic", ["a","b","c"]);
746
+
747
+ var subs = cache[topic],
748
+ len = subs ? subs.length : 0,
749
+ r;
750
+
751
+ //can change loop or reverse array if the order matters
752
+ while(len--){
753
+ r = subs[len].apply(target, args || []);
754
+ if (typeof r === 'boolean') return r;
755
+ }
756
+ };
757
+
758
+ d.subscribe = function(/* String */ topic, /* Function */ callback, /* Boolean */ topPriority){
759
+ // summary:
760
+ // Register a callback on a named topic.
761
+ // topic: String
762
+ // The channel to subscribe to
763
+ // callback: Function
764
+ // The handler event. Anytime something is publish'ed on a
765
+ // subscribed channel, the callback will be called with the
766
+ // published array as ordered arguments.
767
+ //
768
+ // returns: Array
769
+ // A handle which can be used to unsubscribe this particular subscription.
770
+ //
771
+ // example:
772
+ // subscribe("/some/topic", function(a, b, c){ /* handle data */ });
773
+
774
+ if(!cache[topic]){
775
+ cache[topic] = [];
776
+ }
777
+ if (topPriority)
778
+ cache[topic].push(callback);
779
+ else
780
+ cache[topic].unshift(callback);
781
+ return [topic, callback]; // Array
782
+ };
783
+
784
+ d.unsubscribe = function(/* Array */ handle){
785
+ // summary:
786
+ // Disconnect a subscribed function for a topic.
787
+ // handle: Array
788
+ // The return value from a subscribe call.
789
+ // example:
790
+ // var handle = subscribe("/some/topic", function(){});
791
+ // unsubscribe(handle);
792
+
793
+ var subs = cache[handle[0]],
794
+ callback = handle[1],
795
+ len = subs ? subs.length : 0;
796
+
797
+ while(len--){
798
+ if(subs[len] === callback){
799
+ subs.splice(len, 1);
800
+ }
801
+ }
802
+ };
803
+
804
+ };// Moment.js
805
+ // Altered slightly for use in Kalendae.js
806
+ //
807
+ // (c) 2011 Tim Wood
808
+ // Moment.js is freely distributable under the terms of the MIT license.
809
+ //
810
+ // Version 1.3.0
811
+
812
+ var moment = Kalendae.moment = (function (Date, undefined) {
813
+
814
+ var moment,
815
+ round = Math.round,
816
+ languages = {},
817
+ hasModule = (typeof module !== 'undefined'),
818
+ paramsToParse = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'),
819
+ i,
820
+ charactersToReplace = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|zz?|ZZ?|LT|LL?L?L?)/g,
821
+ nonuppercaseLetters = /[^A-Z]/g,
822
+ timezoneRegex = /\([A-Za-z ]+\)|:[0-9]{2} [A-Z]{3} /g,
823
+ tokenCharacters = /(\\)?(MM?M?M?|dd?d?d|DD?D?D?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|ZZ?|T)/g,
824
+ inputCharacters = /(\\)?([0-9]+|([a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|([\+\-]\d\d:?\d\d))/gi,
825
+ timezoneParseRegex = /([\+\-]|\d\d)/gi,
826
+ VERSION = "1.3.0",
827
+ shortcuts = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|');
828
+
829
+ // Moment prototype object
830
+ function Moment(date) {
831
+ this._d = date;
832
+ }
833
+
834
+ // left zero fill a number
835
+ // see http://jsperf.com/left-zero-filling for performance comparison
836
+ function leftZeroFill(number, targetLength) {
837
+ var output = number + '';
838
+ while (output.length < targetLength) {
839
+ output = '0' + output;
840
+ }
841
+ return output;
842
+ }
843
+
844
+ // helper function for _.addTime and _.subtractTime
845
+ function dateAddRemove(date, _input, adding, val) {
846
+ var isString = (typeof _input === 'string'),
847
+ input = isString ? {} : _input,
848
+ ms, d, M, currentDate;
849
+ if (isString && val) {
850
+ input[_input] = val;
851
+ }
852
+ ms = (input.ms || input.milliseconds || 0) +
853
+ (input.s || input.seconds || 0) * 1e3 + // 1000
854
+ (input.m || input.minutes || 0) * 6e4 + // 1000 * 60
855
+ (input.h || input.hours || 0) * 36e5; // 1000 * 60 * 60
856
+ d = (input.d || input.days || 0) +
857
+ (input.w || input.weeks || 0) * 7;
858
+ M = (input.M || input.months || 0) +
859
+ (input.y || input.years || 0) * 12;
860
+ if (ms) {
861
+ date.setTime(+date + ms * adding);
862
+ }
863
+ if (d) {
864
+ date.setDate(date.getDate() + d * adding);
865
+ }
866
+ if (M) {
867
+ currentDate = date.getDate();
868
+ date.setDate(1);
869
+ date.setMonth(date.getMonth() + M * adding);
870
+ date.setDate(Math.min(new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(), currentDate));
871
+ }
872
+ return date;
873
+ }
874
+
875
+ // check if is an array
876
+ function isArray(input) {
877
+ return Object.prototype.toString.call(input) === '[object Array]';
878
+ }
879
+
880
+ // convert an array to a date.
881
+ // the array should mirror the parameters below
882
+ // note: all values past the year are optional and will default to the lowest possible value.
883
+ // [year, month, day , hour, minute, second, millisecond]
884
+ function dateFromArray(input) {
885
+ return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0);
886
+ }
887
+
888
+ // format date using native date object
889
+ function formatDate(date, inputString) {
890
+ var m = new Moment(date),
891
+ currentMonth = m.month(),
892
+ currentDate = m.date(),
893
+ currentYear = m.year(),
894
+ currentDay = m.day(),
895
+ currentHours = m.hours(),
896
+ currentMinutes = m.minutes(),
897
+ currentSeconds = m.seconds(),
898
+ currentZone = m.zone(),
899
+ ordinal = moment.ordinal,
900
+ meridiem = moment.meridiem;
901
+ // check if the character is a format
902
+ // return formatted string or non string.
903
+ //
904
+ // uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380)
905
+ // for minification and performance
906
+ // see http://jsperf.com/object-of-functions-vs-switch for performance comparison
907
+ function replaceFunction(input) {
908
+ // create a couple variables to be used later inside one of the cases.
909
+ var a, b;
910
+ switch (input) {
911
+ // MONTH
912
+ case 'M' :
913
+ return currentMonth + 1;
914
+ case 'Mo' :
915
+ return (currentMonth + 1) + ordinal(currentMonth + 1);
916
+ case 'MM' :
917
+ return leftZeroFill(currentMonth + 1, 2);
918
+ case 'MMM' :
919
+ return moment.monthsShort[currentMonth];
920
+ case 'MMMM' :
921
+ return moment.months[currentMonth];
922
+ // DAY OF MONTH
923
+ case 'D' :
924
+ return currentDate;
925
+ case 'Do' :
926
+ return currentDate + ordinal(currentDate);
927
+ case 'DD' :
928
+ return leftZeroFill(currentDate, 2);
929
+ // DAY OF YEAR
930
+ case 'DDD' :
931
+ a = new Date(currentYear, currentMonth, currentDate);
932
+ b = new Date(currentYear, 0, 1);
933
+ return ~~ (((a - b) / 864e5) + 1.5);
934
+ case 'DDDo' :
935
+ a = replaceFunction('DDD');
936
+ return a + ordinal(a);
937
+ case 'DDDD' :
938
+ return leftZeroFill(replaceFunction('DDD'), 3);
939
+ // WEEKDAY
940
+ case 'd' :
941
+ return currentDay;
942
+ case 'do' :
943
+ return currentDay + ordinal(currentDay);
944
+ case 'ddd' :
945
+ return moment.weekdaysShort[currentDay];
946
+ case 'dddd' :
947
+ return moment.weekdays[currentDay];
948
+ // WEEK OF YEAR
949
+ case 'w' :
950
+ a = new Date(currentYear, currentMonth, currentDate - currentDay + 5);
951
+ b = new Date(a.getFullYear(), 0, 4);
952
+ return ~~ ((a - b) / 864e5 / 7 + 1.5);
953
+ case 'wo' :
954
+ a = replaceFunction('w');
955
+ return a + ordinal(a);
956
+ case 'ww' :
957
+ return leftZeroFill(replaceFunction('w'), 2);
958
+ // YEAR
959
+ case 'YY' :
960
+ return leftZeroFill(currentYear % 100, 2);
961
+ case 'YYYY' :
962
+ return currentYear;
963
+ // AM / PM
964
+ case 'a' :
965
+ return currentHours > 11 ? meridiem.pm : meridiem.am;
966
+ case 'A' :
967
+ return currentHours > 11 ? meridiem.PM : meridiem.AM;
968
+ // 24 HOUR
969
+ case 'H' :
970
+ return currentHours;
971
+ case 'HH' :
972
+ return leftZeroFill(currentHours, 2);
973
+ // 12 HOUR
974
+ case 'h' :
975
+ return currentHours % 12 || 12;
976
+ case 'hh' :
977
+ return leftZeroFill(currentHours % 12 || 12, 2);
978
+ // MINUTE
979
+ case 'm' :
980
+ return currentMinutes;
981
+ case 'mm' :
982
+ return leftZeroFill(currentMinutes, 2);
983
+ // SECOND
984
+ case 's' :
985
+ return currentSeconds;
986
+ case 'ss' :
987
+ return leftZeroFill(currentSeconds, 2);
988
+ // TIMEZONE
989
+ case 'zz' :
990
+ // depreciating 'zz' fall through to 'z'
991
+ case 'z' :
992
+ return (date.toString().match(timezoneRegex) || [''])[0].replace(nonuppercaseLetters, '');
993
+ case 'Z' :
994
+ return (currentZone > 0 ? '+' : '-') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2);
995
+ case 'ZZ' :
996
+ return (currentZone > 0 ? '+' : '-') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4);
997
+ // LONG DATES
998
+ case 'L' :
999
+ case 'LL' :
1000
+ case 'LLL' :
1001
+ case 'LLLL' :
1002
+ case 'LT' :
1003
+ return formatDate(date, moment.longDateFormat[input]);
1004
+ // DEFAULT
1005
+ default :
1006
+ return input.replace(/(^\[)|(\\)|\]$/g, "");
1007
+ }
1008
+ }
1009
+ return inputString.replace(charactersToReplace, replaceFunction);
1010
+ }
1011
+
1012
+ // date from string and format string
1013
+ function makeDateFromStringAndFormat(string, format) {
1014
+ var inArray = [0, 0, 1, 0, 0, 0, 0],
1015
+ timezoneHours = 0,
1016
+ timezoneMinutes = 0,
1017
+ isUsingUTC = false,
1018
+ inputParts = string.match(inputCharacters),
1019
+ formatParts = format.match(tokenCharacters),
1020
+ i,
1021
+ isPm;
1022
+
1023
+ // function to convert string input to date
1024
+ function addTime(format, input) {
1025
+ var a;
1026
+ switch (format) {
1027
+ // MONTH
1028
+ case 'M' :
1029
+ // fall through to MM
1030
+ case 'MM' :
1031
+ inArray[1] = ~~input - 1;
1032
+ break;
1033
+ case 'MMM' :
1034
+ // fall through to MMMM
1035
+ case 'MMMM' :
1036
+ for (a = 0; a < 12; a++) {
1037
+ if (moment.monthsParse[a].test(input)) {
1038
+ inArray[1] = a;
1039
+ break;
1040
+ }
1041
+ }
1042
+ break;
1043
+ // DAY OF MONTH
1044
+ case 'D' :
1045
+ // fall through to DDDD
1046
+ case 'DD' :
1047
+ // fall through to DDDD
1048
+ case 'DDD' :
1049
+ // fall through to DDDD
1050
+ case 'DDDD' :
1051
+ inArray[2] = ~~input;
1052
+ break;
1053
+ // YEAR
1054
+ case 'YY' :
1055
+ input = ~~input;
1056
+ inArray[0] = input + (input > 70 ? 1900 : 2000);
1057
+ break;
1058
+ case 'YYYY' :
1059
+ inArray[0] = ~~Math.abs(input);
1060
+ break;
1061
+ // AM / PM
1062
+ case 'a' :
1063
+ // fall through to A
1064
+ case 'A' :
1065
+ isPm = (input.toLowerCase() === 'pm');
1066
+ break;
1067
+ // 24 HOUR
1068
+ case 'H' :
1069
+ // fall through to hh
1070
+ case 'HH' :
1071
+ // fall through to hh
1072
+ case 'h' :
1073
+ // fall through to hh
1074
+ case 'hh' :
1075
+ inArray[3] = ~~input;
1076
+ break;
1077
+ // MINUTE
1078
+ case 'm' :
1079
+ // fall through to mm
1080
+ case 'mm' :
1081
+ inArray[4] = ~~input;
1082
+ break;
1083
+ // SECOND
1084
+ case 's' :
1085
+ // fall through to ss
1086
+ case 'ss' :
1087
+ inArray[5] = ~~input;
1088
+ break;
1089
+ // TIMEZONE
1090
+ case 'Z' :
1091
+ // fall through to ZZ
1092
+ case 'ZZ' :
1093
+ isUsingUTC = true;
1094
+ a = input.match(timezoneParseRegex);
1095
+ if (a[1]) {
1096
+ timezoneHours = ~~a[1];
1097
+ }
1098
+ if (a[2]) {
1099
+ timezoneMinutes = ~~a[2];
1100
+ }
1101
+ // reverse offsets
1102
+ if (a[0] === '-') {
1103
+ timezoneHours = -timezoneHours;
1104
+ timezoneMinutes = -timezoneMinutes;
1105
+ }
1106
+ break;
1107
+ }
1108
+ }
1109
+ for (i = 0; i < formatParts.length; i++) {
1110
+ addTime(formatParts[i], inputParts[i]);
1111
+ }
1112
+ // handle am pm
1113
+ if (isPm && inArray[3] < 12) {
1114
+ inArray[3] += 12;
1115
+ }
1116
+ // if is 12 am, change hours to 0
1117
+ if (isPm === false && inArray[3] === 12) {
1118
+ inArray[3] = 0;
1119
+ }
1120
+ // handle timezone
1121
+ inArray[3] += timezoneHours;
1122
+ inArray[4] += timezoneMinutes;
1123
+ // return
1124
+ return isUsingUTC ? new Date(Date.UTC.apply({}, inArray)) : dateFromArray(inArray);
1125
+ }
1126
+
1127
+ // compare two arrays, return the number of differences
1128
+ function compareArrays(array1, array2) {
1129
+ var len = Math.min(array1.length, array2.length),
1130
+ lengthDiff = Math.abs(array1.length - array2.length),
1131
+ diffs = 0,
1132
+ i;
1133
+ for (i = 0; i < len; i++) {
1134
+ if (~~array1[i] !== ~~array2[i]) {
1135
+ diffs++;
1136
+ }
1137
+ }
1138
+ return diffs + lengthDiff;
1139
+ }
1140
+
1141
+ // date from string and array of format strings
1142
+ function makeDateFromStringAndArray(string, formats) {
1143
+ var output,
1144
+ inputParts = string.match(inputCharacters),
1145
+ scores = [],
1146
+ scoreToBeat = 99,
1147
+ i,
1148
+ curDate,
1149
+ curScore;
1150
+ for (i = 0; i < formats.length; i++) {
1151
+ curDate = makeDateFromStringAndFormat(string, formats[i]);
1152
+ curScore = compareArrays(inputParts, formatDate(curDate, formats[i]).match(inputCharacters));
1153
+ if (curScore < scoreToBeat) {
1154
+ scoreToBeat = curScore;
1155
+ output = curDate;
1156
+ }
1157
+ }
1158
+ return output;
1159
+ }
1160
+
1161
+ moment = function (input, format) {
1162
+ if (input === null) {
1163
+ return null;
1164
+ }
1165
+ var date;
1166
+ // parse UnderscoreDate object
1167
+ if (input && input._d instanceof Date) {
1168
+ date = new Date(+input._d);
1169
+ // parse string and format
1170
+ } else if (format) {
1171
+ if (isArray(format)) {
1172
+ date = makeDateFromStringAndArray(input, format);
1173
+ } else {
1174
+ date = makeDateFromStringAndFormat(input, format);
1175
+ }
1176
+ // parse everything else
1177
+ } else {
1178
+ date = input === undefined ? new Date() :
1179
+ input instanceof Date ? input :
1180
+ isArray(input) ? dateFromArray(input) :
1181
+ new Date(input);
1182
+ }
1183
+ return new Moment(date);
1184
+ };
1185
+
1186
+ // version number
1187
+ moment.version = VERSION;
1188
+
1189
+ // language switching and caching
1190
+ moment.lang = function (key, values) {
1191
+ var i,
1192
+ param,
1193
+ req,
1194
+ parse = [];
1195
+ if (values) {
1196
+ for (i = 0; i < 12; i++) {
1197
+ parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i');
1198
+ }
1199
+ values.monthsParse = values.monthsParse || parse;
1200
+ languages[key] = values;
1201
+ }
1202
+ if (languages[key]) {
1203
+ for (i = 0; i < paramsToParse.length; i++) {
1204
+ param = paramsToParse[i];
1205
+ moment[param] = languages[key][param] || moment[param];
1206
+ }
1207
+ } else {
1208
+ if (hasModule) {
1209
+ req = require('./lang/' + key);
1210
+ moment.lang(key, req);
1211
+ }
1212
+ }
1213
+ };
1214
+
1215
+ // set default language
1216
+ moment.lang('en', {
1217
+ months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
1218
+ monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
1219
+ weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
1220
+ weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
1221
+ longDateFormat : {
1222
+ LT : "h:mm A",
1223
+ L : "MM/DD/YYYY",
1224
+ LL : "MMMM D YYYY",
1225
+ LLL : "MMMM D YYYY LT",
1226
+ LLLL : "dddd, MMMM D YYYY LT"
1227
+ },
1228
+ meridiem : {
1229
+ AM : 'AM',
1230
+ am : 'am',
1231
+ PM : 'PM',
1232
+ pm : 'pm'
1233
+ },
1234
+ calendar : {
1235
+ sameDay : '[Today at] LT',
1236
+ nextDay : '[Tomorrow at] LT',
1237
+ nextWeek : 'dddd [at] LT',
1238
+ lastDay : '[Yesterday at] LT',
1239
+ lastWeek : '[last] dddd [at] LT',
1240
+ sameElse : 'L'
1241
+ },
1242
+ relativeTime : {
1243
+ future : "in %s",
1244
+ past : "%s ago",
1245
+ s : "a few seconds",
1246
+ m : "a minute",
1247
+ mm : "%d minutes",
1248
+ h : "an hour",
1249
+ hh : "%d hours",
1250
+ d : "a day",
1251
+ dd : "%d days",
1252
+ M : "a month",
1253
+ MM : "%d months",
1254
+ y : "a year",
1255
+ yy : "%d years"
1256
+ },
1257
+ ordinal : function (number) {
1258
+ var b = number % 10;
1259
+ return (~~ (number % 100 / 10) === 1) ? 'th' :
1260
+ (b === 1) ? 'st' :
1261
+ (b === 2) ? 'nd' :
1262
+ (b === 3) ? 'rd' : 'th';
1263
+ }
1264
+ });
1265
+
1266
+ // helper function for _date.from() and _date.fromNow()
1267
+ function substituteTimeAgo(string, number, withoutSuffix) {
1268
+ var rt = moment.relativeTime[string];
1269
+ return (typeof rt === 'function') ?
1270
+ rt(number || 1, !!withoutSuffix, string) :
1271
+ rt.replace(/%d/i, number || 1);
1272
+ }
1273
+
1274
+ function relativeTime(milliseconds, withoutSuffix) {
1275
+ var seconds = round(Math.abs(milliseconds) / 1000),
1276
+ minutes = round(seconds / 60),
1277
+ hours = round(minutes / 60),
1278
+ days = round(hours / 24),
1279
+ years = round(days / 365),
1280
+ args = seconds < 45 && ['s', seconds] ||
1281
+ minutes === 1 && ['m'] ||
1282
+ minutes < 45 && ['mm', minutes] ||
1283
+ hours === 1 && ['h'] ||
1284
+ hours < 22 && ['hh', hours] ||
1285
+ days === 1 && ['d'] ||
1286
+ days <= 25 && ['dd', days] ||
1287
+ days <= 45 && ['M'] ||
1288
+ days < 345 && ['MM', round(days / 30)] ||
1289
+ years === 1 && ['y'] || ['yy', years];
1290
+ args[2] = withoutSuffix;
1291
+ return substituteTimeAgo.apply({}, args);
1292
+ }
1293
+
1294
+ // shortcut for prototype
1295
+ moment.fn = Moment.prototype = {
1296
+
1297
+ clone : function () {
1298
+ return moment(this);
1299
+ },
1300
+
1301
+ valueOf : function () {
1302
+ return +this._d;
1303
+ },
1304
+
1305
+ nativeDate : function () {
1306
+ return this._d;
1307
+ },
1308
+
1309
+ toString : function () {
1310
+ return this._d.toString();
1311
+ },
1312
+
1313
+ toDate : function () {
1314
+ return this._d;
1315
+ },
1316
+
1317
+ format : function (inputString) {
1318
+ return formatDate(this._d, inputString);
1319
+ },
1320
+
1321
+ add : function (input, val) {
1322
+ this._d = dateAddRemove(this._d, input, 1, val);
1323
+ return this;
1324
+ },
1325
+
1326
+ subtract : function (input, val) {
1327
+ this._d = dateAddRemove(this._d, input, -1, val);
1328
+ return this;
1329
+ },
1330
+
1331
+ diff : function (input, val, asFloat) {
1332
+ var inputMoment = moment(input),
1333
+ diff = this._d - inputMoment._d,
1334
+ year = this.year() - inputMoment.year(),
1335
+ month = this.month() - inputMoment.month(),
1336
+ day = this.day() - inputMoment.day(),
1337
+ output;
1338
+ if (val === 'months') {
1339
+ output = year * 12 + month + day / 30;
1340
+ } else if (val === 'years') {
1341
+ output = year + month / 12;
1342
+ } else {
1343
+ output = val === 'seconds' ? diff / 1e3 : // 1000
1344
+ val === 'minutes' ? diff / 6e4 : // 1000 * 60
1345
+ val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60
1346
+ val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24
1347
+ val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
1348
+ val === 'days' ? diff / 3600 : diff;
1349
+ }
1350
+ return asFloat ? output : round(output);
1351
+ },
1352
+
1353
+ from : function (time, withoutSuffix) {
1354
+ var difference = this.diff(time),
1355
+ rel = moment.relativeTime,
1356
+ output = relativeTime(difference, withoutSuffix);
1357
+ return withoutSuffix ? output : (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output);
1358
+ },
1359
+
1360
+ fromNow : function (withoutSuffix) {
1361
+ return this.from(moment(), withoutSuffix);
1362
+ },
1363
+
1364
+ calendar : function () {
1365
+ var today = moment(),
1366
+ todayAtZeroHour = moment([today.year(), today.month(), today.date()]),
1367
+ diff = this.diff(todayAtZeroHour, 'days', true),
1368
+ calendar = moment.calendar,
1369
+ allElse = calendar.sameElse,
1370
+ format = diff < -6 ? allElse :
1371
+ diff < -1 ? calendar.lastWeek :
1372
+ diff < 0 ? calendar.lastDay :
1373
+ diff < 1 ? calendar.sameDay :
1374
+ diff < 2 ? calendar.nextDay :
1375
+ diff < 7 ? calendar.nextWeek : allElse;
1376
+ return this.format(typeof format === 'function' ? format.apply(this) : format);
1377
+ },
1378
+
1379
+ isLeapYear : function () {
1380
+ var year = this.year();
1381
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
1382
+ },
1383
+
1384
+ isDST : function () {
1385
+ return this.zone() !== moment([this.year()]).zone();
1386
+ },
1387
+
1388
+ day : function (input) {
1389
+ var day = this._d.getDay();
1390
+ return input == null ? day :
1391
+ this.add({ d : input - day });
1392
+ }
1393
+ };
1394
+
1395
+ // helper for adding shortcuts
1396
+ function makeShortcut(name, key) {
1397
+ moment.fn[name] = function (input) {
1398
+ if (input != null) {
1399
+ this._d['set' + key](input);
1400
+ return this;
1401
+ } else {
1402
+ return this._d['get' + key]();
1403
+ }
1404
+ };
1405
+ }
1406
+
1407
+ // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
1408
+ for (i = 0; i < shortcuts.length; i ++) {
1409
+ makeShortcut(shortcuts[i].toLowerCase(), shortcuts[i]);
1410
+ }
1411
+
1412
+ // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
1413
+ makeShortcut('year', 'FullYear');
1414
+
1415
+ // add shortcut for timezone offset (no setter)
1416
+ moment.fn.zone = function () {
1417
+ return this._d.getTimezoneOffset();
1418
+ };
1419
+
1420
+ return moment;
1421
+ })(Date);
1422
+
1423
+ today = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
1424
+
1425
+ if (typeof jQuery !== 'undefined') {
1426
+ jQuery.fn.kalendae = function (options) {
1427
+ this.each(function (i, e) {
1428
+ if (e.tagName === 'INPUT') {
1429
+ //if element is an input, bind a popup calendar to the input.
1430
+ $(e).data('kalendae', new Kalendae.Input(e, options));
1431
+ } else {
1432
+ //otherwise, insert a flat calendar into the element.
1433
+ $(e).data('kalendae', new Kalendae($.extend({}, {attachTo:e}, options)));
1434
+ }
1435
+ });
1436
+ return this;
1437
+ }
1438
+ }
1439
+
1440
+
1441
+ })();