kalendae_assets 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ })();