datatables.net-datetime 1.1.2 → 1.3.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,1628 @@
1
+ /*! DateTime picker for DataTables.net v1.3.0
2
+ *
3
+ * © SpryMedia Ltd, all rights reserved.
4
+ * License: MIT datatables.net/license/mit
5
+ */
6
+
7
+ import $ from 'jquery';
8
+
9
+
10
+
11
+ /**
12
+ * @summary DateTime picker for DataTables.net
13
+ * @version 1.3.0
14
+ * @file dataTables.dateTime.js
15
+ * @author SpryMedia Ltd
16
+ * @contact www.datatables.net/contact
17
+ */
18
+
19
+ // Supported formatting and parsing libraries:
20
+ // * Moment
21
+ // * Luxon
22
+ // * DayJS
23
+ var dateLib;
24
+
25
+ /*
26
+ * This file provides a DateTime GUI picker (calendar and time input). Only the
27
+ * format YYYY-MM-DD is supported without additional software, but the end user
28
+ * experience can be greatly enhanced by including the momentjs, dayjs or luxon library
29
+ * which provide date / time parsing and formatting options.
30
+ *
31
+ * This functionality is required because the HTML5 date and datetime input
32
+ * types are not widely supported in desktop browsers.
33
+ *
34
+ * Constructed by using:
35
+ *
36
+ * new DateTime( input, opts )
37
+ *
38
+ * where `input` is the HTML input element to use and `opts` is an object of
39
+ * options based on the `DateTime.defaults` object.
40
+ */
41
+ var DateTime = function ( input, opts ) {
42
+ // Attempt to auto detect the formatting library (if there is one). Having it in
43
+ // the constructor allows load order independence.
44
+ if (typeof dateLib === 'undefined') {
45
+ dateLib = window.moment
46
+ ? window.moment
47
+ : window.dayjs
48
+ ? window.dayjs
49
+ : window.luxon
50
+ ? window.luxon
51
+ : null;
52
+ }
53
+
54
+ this.c = $.extend( true, {}, DateTime.defaults, opts );
55
+ var classPrefix = this.c.classPrefix;
56
+ var i18n = this.c.i18n;
57
+
58
+ // Only IS8601 dates are supported without moment, dayjs or luxon
59
+ if ( ! dateLib && this.c.format !== 'YYYY-MM-DD' ) {
60
+ throw "DateTime: Without momentjs, dayjs or luxon only the format 'YYYY-MM-DD' can be used";
61
+ }
62
+
63
+ // Min and max need to be `Date` objects in the config
64
+ if (typeof this.c.minDate === 'string') {
65
+ this.c.minDate = new Date(this.c.minDate);
66
+ }
67
+ if (typeof this.c.maxDate === 'string') {
68
+ this.c.maxDate = new Date(this.c.maxDate);
69
+ }
70
+
71
+ // DOM structure
72
+ var structure = $(
73
+ '<div class="'+classPrefix+'">'+
74
+ '<div class="'+classPrefix+'-date">'+
75
+ '<div class="'+classPrefix+'-title">'+
76
+ '<div class="'+classPrefix+'-iconLeft">'+
77
+ '<button type="button"></button>'+
78
+ '</div>'+
79
+ '<div class="'+classPrefix+'-iconRight">'+
80
+ '<button type="button"></button>'+
81
+ '</div>'+
82
+ '<div class="'+classPrefix+'-label">'+
83
+ '<span></span>'+
84
+ '<select class="'+classPrefix+'-month"></select>'+
85
+ '</div>'+
86
+ '<div class="'+classPrefix+'-label">'+
87
+ '<span></span>'+
88
+ '<select class="'+classPrefix+'-year"></select>'+
89
+ '</div>'+
90
+ '</div>'+
91
+ '<div class="'+classPrefix+'-buttons">'+
92
+ '<a class="'+classPrefix+'-clear"></a>'+
93
+ '<a class="'+classPrefix+'-today"></a>'+
94
+ '</div>'+
95
+ '<div class="'+classPrefix+'-calendar"></div>'+
96
+ '</div>'+
97
+ '<div class="'+classPrefix+'-time">'+
98
+ '<div class="'+classPrefix+'-hours"></div>'+
99
+ '<div class="'+classPrefix+'-minutes"></div>'+
100
+ '<div class="'+classPrefix+'-seconds"></div>'+
101
+ '</div>'+
102
+ '<div class="'+classPrefix+'-error"></div>'+
103
+ '</div>'
104
+ );
105
+
106
+ this.dom = {
107
+ container: structure,
108
+ date: structure.find( '.'+classPrefix+'-date' ),
109
+ title: structure.find( '.'+classPrefix+'-title' ),
110
+ calendar: structure.find( '.'+classPrefix+'-calendar' ),
111
+ time: structure.find( '.'+classPrefix+'-time' ),
112
+ error: structure.find( '.'+classPrefix+'-error' ),
113
+ buttons: structure.find( '.'+classPrefix+'-buttons' ),
114
+ clear: structure.find( '.'+classPrefix+'-clear' ),
115
+ today: structure.find( '.'+classPrefix+'-today' ),
116
+ previous: structure.find( '.'+classPrefix+'-iconLeft' ),
117
+ next: structure.find( '.'+classPrefix+'-iconRight' ),
118
+ input: $(input)
119
+ };
120
+
121
+ this.s = {
122
+ /** @type {Date} Date value that the picker has currently selected */
123
+ d: null,
124
+
125
+ /** @type {Date} Date of the calendar - might not match the value */
126
+ display: null,
127
+
128
+ /** @type {number} Used to select minutes in a range where the range base is itself unavailable */
129
+ minutesRange: null,
130
+
131
+ /** @type {number} Used to select minutes in a range where the range base is itself unavailable */
132
+ secondsRange: null,
133
+
134
+ /** @type {String} Unique namespace string for this instance */
135
+ namespace: 'dateime-'+(DateTime._instance++),
136
+
137
+ /** @type {Object} Parts of the picker that should be shown */
138
+ parts: {
139
+ date: this.c.format.match( /[YMD]|L(?!T)|l/ ) !== null,
140
+ time: this.c.format.match( /[Hhm]|LT|LTS/ ) !== null,
141
+ seconds: this.c.format.indexOf( 's' ) !== -1,
142
+ hours12: this.c.format.match( /[haA]/ ) !== null
143
+ }
144
+ };
145
+
146
+ this.dom.container
147
+ .append( this.dom.date )
148
+ .append( this.dom.time )
149
+ .append( this.dom.error );
150
+
151
+ this.dom.date
152
+ .append( this.dom.title )
153
+ .append( this.dom.buttons )
154
+ .append( this.dom.calendar );
155
+
156
+ this._constructor();
157
+ };
158
+
159
+ $.extend( DateTime.prototype, {
160
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
161
+ * Public
162
+ */
163
+
164
+ /**
165
+ * Destroy the control
166
+ */
167
+ destroy: function () {
168
+ this._hide(true);
169
+ this.dom.container.off().empty();
170
+ this.dom.input
171
+ .removeAttr('autocomplete')
172
+ .off('.datetime');
173
+ },
174
+
175
+ errorMsg: function ( msg ) {
176
+ var error = this.dom.error;
177
+
178
+ if ( msg ) {
179
+ error.html( msg );
180
+ }
181
+ else {
182
+ error.empty();
183
+ }
184
+
185
+ return this;
186
+ },
187
+
188
+ hide: function () {
189
+ this._hide();
190
+
191
+ return this;
192
+ },
193
+
194
+ max: function ( date ) {
195
+ this.c.maxDate = typeof date === 'string'
196
+ ? new Date(date)
197
+ : date;
198
+
199
+ this._optionsTitle();
200
+ this._setCalander();
201
+
202
+ return this;
203
+ },
204
+
205
+ min: function ( date ) {
206
+ this.c.minDate = typeof date === 'string'
207
+ ? new Date(date)
208
+ : date;
209
+
210
+ this._optionsTitle();
211
+ this._setCalander();
212
+
213
+ return this;
214
+ },
215
+
216
+ /**
217
+ * Check if an element belongs to this control
218
+ *
219
+ * @param {node} node Element to check
220
+ * @return {boolean} true if owned by this control, false otherwise
221
+ */
222
+ owns: function ( node ) {
223
+ return $(node).parents().filter( this.dom.container ).length > 0;
224
+ },
225
+
226
+ /**
227
+ * Get / set the value
228
+ *
229
+ * @param {string|Date} set Value to set
230
+ * @param {boolean} [write=true] Flag to indicate if the formatted value
231
+ * should be written into the input element
232
+ */
233
+ val: function ( set, write ) {
234
+ if ( set === undefined ) {
235
+ return this.s.d;
236
+ }
237
+
238
+ if ( set instanceof Date ) {
239
+ this.s.d = this._dateToUtc( set );
240
+ }
241
+ else if ( set === null || set === '' ) {
242
+ this.s.d = null;
243
+ }
244
+ else if ( set === '--now' ) {
245
+ this.s.d = this._dateToUtc(new Date());
246
+ }
247
+ else if ( typeof set === 'string' ) {
248
+ this.s.d = this._convert(set, this.c.format, null);
249
+ }
250
+
251
+ if ( write || write === undefined ) {
252
+ if ( this.s.d ) {
253
+ this._writeOutput();
254
+ }
255
+ else {
256
+ // The input value was not valid...
257
+ this.dom.input.val( set );
258
+ }
259
+ }
260
+
261
+ // Need something to display
262
+ this.s.display = this.s.d
263
+ ? new Date( this.s.d.toString() )
264
+ : new Date();
265
+
266
+ // Set the day of the month to be 1 so changing between months doesn't
267
+ // run into issues when going from day 31 to 28 (for example)
268
+ this.s.display.setUTCDate( 1 );
269
+
270
+ // Update the display elements for the new value
271
+ this._setTitle();
272
+ this._setCalander();
273
+ this._setTime();
274
+
275
+ return this;
276
+ },
277
+
278
+ /**
279
+ * Similar to `val()` but uses a given date / time format
280
+ *
281
+ * @param format Format to get the data as (getter) or that is input (setter)
282
+ * @param val Value to write (if undefined, used as a getter)
283
+ * @returns
284
+ */
285
+ valFormat: function (format, val) {
286
+ if (! val) {
287
+ return this._convert(this.val(), null, format);
288
+ }
289
+
290
+ // Convert from the format given here to the instance's configured format
291
+ this.val(
292
+ this._convert(val, format, null)
293
+ );
294
+
295
+ return this;
296
+ },
297
+
298
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
299
+ * Constructor
300
+ */
301
+
302
+ /**
303
+ * Build the control and assign initial event handlers
304
+ *
305
+ * @private
306
+ */
307
+ _constructor: function () {
308
+ var that = this;
309
+ var classPrefix = this.c.classPrefix;
310
+ var last = this.dom.input.val();
311
+
312
+ var onChange = function () {
313
+ var curr = that.dom.input.val();
314
+
315
+ if (curr !== last) {
316
+ that.c.onChange.call( that, curr, that.s.d, that.dom.input );
317
+ last = curr;
318
+ }
319
+ };
320
+
321
+ if ( ! this.s.parts.date ) {
322
+ this.dom.date.css( 'display', 'none' );
323
+ }
324
+
325
+ if ( ! this.s.parts.time ) {
326
+ this.dom.time.css( 'display', 'none' );
327
+ }
328
+
329
+ if ( ! this.s.parts.seconds ) {
330
+ this.dom.time.children('div.'+classPrefix+'-seconds').remove();
331
+ this.dom.time.children('span').eq(1).remove();
332
+ }
333
+
334
+ if ( ! this.c.buttons.clear ) {
335
+ this.dom.clear.css( 'display', 'none' );
336
+ }
337
+
338
+ if ( ! this.c.buttons.today ) {
339
+ this.dom.today.css( 'display', 'none' );
340
+ }
341
+
342
+ // Render the options
343
+ this._optionsTitle();
344
+
345
+ $(document).on('i18n.dt', function (e, settings) {
346
+ if (settings.oLanguage.datetime) {
347
+ $.extend(true, that.c.i18n, settings.oLanguage.datetime);
348
+ that._optionsTitle();
349
+ }
350
+ });
351
+
352
+ // When attached to a hidden input, we always show the input picker, and
353
+ // do so inline
354
+ if (this.dom.input.attr('type') === 'hidden') {
355
+ this.dom.container.addClass('inline');
356
+ this.c.attachTo = 'input';
357
+
358
+ this.val( this.dom.input.val(), false );
359
+ this._show();
360
+ }
361
+
362
+ // Set the initial value
363
+ if (last) {
364
+ this.val( last, false );
365
+ }
366
+
367
+ // Trigger the display of the widget when clicking or focusing on the
368
+ // input element
369
+ this.dom.input
370
+ .attr('autocomplete', 'off')
371
+ .on('focus.datetime click.datetime', function () {
372
+ // If already visible - don't do anything
373
+ if ( that.dom.container.is(':visible') || that.dom.input.is(':disabled') ) {
374
+ return;
375
+ }
376
+
377
+ // In case the value has changed by text
378
+ that.val( that.dom.input.val(), false );
379
+
380
+ that._show();
381
+ } )
382
+ .on('keyup.datetime', function () {
383
+ // Update the calendar's displayed value as the user types
384
+ if ( that.dom.container.is(':visible') ) {
385
+ that.val( that.dom.input.val(), false );
386
+ }
387
+ } );
388
+
389
+ // Main event handlers for input in the widget
390
+ this.dom.container
391
+ .on( 'change', 'select', function () {
392
+ var select = $(this);
393
+ var val = select.val();
394
+
395
+ if ( select.hasClass(classPrefix+'-month') ) {
396
+ // Month select
397
+ that._correctMonth( that.s.display, val );
398
+ that._setTitle();
399
+ that._setCalander();
400
+ }
401
+ else if ( select.hasClass(classPrefix+'-year') ) {
402
+ // Year select
403
+ that.s.display.setUTCFullYear( val );
404
+ that._setTitle();
405
+ that._setCalander();
406
+ }
407
+ else if ( select.hasClass(classPrefix+'-hours') || select.hasClass(classPrefix+'-ampm') ) {
408
+ // Hours - need to take account of AM/PM input if present
409
+ if ( that.s.parts.hours12 ) {
410
+ var hours = $(that.dom.container).find('.'+classPrefix+'-hours').val() * 1;
411
+ var pm = $(that.dom.container).find('.'+classPrefix+'-ampm').val() === 'pm';
412
+
413
+ that.s.d.setUTCHours( hours === 12 && !pm ?
414
+ 0 :
415
+ pm && hours !== 12 ?
416
+ hours + 12 :
417
+ hours
418
+ );
419
+ }
420
+ else {
421
+ that.s.d.setUTCHours( val );
422
+ }
423
+
424
+ that._setTime();
425
+ that._writeOutput( true );
426
+
427
+ onChange();
428
+ }
429
+ else if ( select.hasClass(classPrefix+'-minutes') ) {
430
+ // Minutes select
431
+ that.s.d.setUTCMinutes( val );
432
+ that._setTime();
433
+ that._writeOutput( true );
434
+
435
+ onChange();
436
+ }
437
+ else if ( select.hasClass(classPrefix+'-seconds') ) {
438
+ // Seconds select
439
+ that.s.d.setSeconds( val );
440
+ that._setTime();
441
+ that._writeOutput( true );
442
+
443
+ onChange();
444
+ }
445
+
446
+ that.dom.input.focus();
447
+ that._position();
448
+ } )
449
+ .on( 'click', function (e) {
450
+ var d = that.s.d;
451
+ var nodeName = e.target.nodeName.toLowerCase();
452
+ var target = nodeName === 'span' ?
453
+ e.target.parentNode :
454
+ e.target;
455
+
456
+ nodeName = target.nodeName.toLowerCase();
457
+
458
+ if ( nodeName === 'select' ) {
459
+ return;
460
+ }
461
+
462
+ e.stopPropagation();
463
+
464
+ if ( nodeName === 'a' ) {
465
+ e.preventDefault();
466
+
467
+ if ($(target).hasClass(classPrefix+'-clear')) {
468
+ // Clear the value and don't change the display
469
+ that.s.d = null;
470
+ that.dom.input.val('');
471
+ that._writeOutput();
472
+ that._setCalander();
473
+ that._setTime();
474
+
475
+ onChange();
476
+ }
477
+ else if ($(target).hasClass(classPrefix+'-today')) {
478
+ // Don't change the value, but jump to the month
479
+ // containing today
480
+ that.s.display = new Date();
481
+
482
+ that._setTitle();
483
+ that._setCalander();
484
+ }
485
+ }
486
+ if ( nodeName === 'button' ) {
487
+ var button = $(target);
488
+ var parent = button.parent();
489
+
490
+ if ( parent.hasClass('disabled') && ! parent.hasClass('range') ) {
491
+ button.blur();
492
+ return;
493
+ }
494
+
495
+ if ( parent.hasClass(classPrefix+'-iconLeft') ) {
496
+ // Previous month
497
+ that.s.display.setUTCMonth( that.s.display.getUTCMonth()-1 );
498
+ that._setTitle();
499
+ that._setCalander();
500
+
501
+ that.dom.input.focus();
502
+ }
503
+ else if ( parent.hasClass(classPrefix+'-iconRight') ) {
504
+ // Next month
505
+ that._correctMonth( that.s.display, that.s.display.getUTCMonth()+1 );
506
+ that._setTitle();
507
+ that._setCalander();
508
+
509
+ that.dom.input.focus();
510
+ }
511
+ else if ( button.parents('.'+classPrefix+'-time').length ) {
512
+ var val = button.data('value');
513
+ var unit = button.data('unit');
514
+
515
+ d = that._needValue();
516
+
517
+ if ( unit === 'minutes' ) {
518
+ if ( parent.hasClass('disabled') && parent.hasClass('range') ) {
519
+ that.s.minutesRange = val;
520
+ that._setTime();
521
+ return;
522
+ }
523
+ else {
524
+ that.s.minutesRange = null;
525
+ }
526
+ }
527
+
528
+ if ( unit === 'seconds' ) {
529
+ if ( parent.hasClass('disabled') && parent.hasClass('range') ) {
530
+ that.s.secondsRange = val;
531
+ that._setTime();
532
+ return;
533
+ }
534
+ else {
535
+ that.s.secondsRange = null;
536
+ }
537
+ }
538
+
539
+ // Specific to hours for 12h clock
540
+ if ( val === 'am' ) {
541
+ if ( d.getUTCHours() >= 12 ) {
542
+ val = d.getUTCHours() - 12;
543
+ }
544
+ else {
545
+ return;
546
+ }
547
+ }
548
+ else if ( val === 'pm' ) {
549
+ if ( d.getUTCHours() < 12 ) {
550
+ val = d.getUTCHours() + 12;
551
+ }
552
+ else {
553
+ return;
554
+ }
555
+ }
556
+
557
+ var set = unit === 'hours' ?
558
+ 'setUTCHours' :
559
+ unit === 'minutes' ?
560
+ 'setUTCMinutes' :
561
+ 'setSeconds';
562
+
563
+ d[set]( val );
564
+ that._setCalander();
565
+ that._setTime();
566
+ that._writeOutput( true );
567
+ onChange();
568
+ }
569
+ else {
570
+ // Calendar click
571
+ d = that._needValue();
572
+
573
+ // Can't be certain that the current day will exist in
574
+ // the new month, and likewise don't know that the
575
+ // new day will exist in the old month, But 1 always
576
+ // does, so we can change the month without worry of a
577
+ // recalculation being done automatically by `Date`
578
+ d.setUTCDate( 1 );
579
+ d.setUTCFullYear( button.data('year') );
580
+ d.setUTCMonth( button.data('month') );
581
+ d.setUTCDate( button.data('day') );
582
+
583
+ that._writeOutput( true );
584
+
585
+ // Don't hide if there is a time picker, since we want to
586
+ // be able to select a time as well.
587
+ if ( ! that.s.parts.time ) {
588
+ // This is annoying but IE has some kind of async
589
+ // behaviour with focus and the focus from the above
590
+ // write would occur after this hide - resulting in the
591
+ // calendar opening immediately
592
+ setTimeout( function () {
593
+ that._hide();
594
+ }, 10 );
595
+ }
596
+ else {
597
+ that._setCalander();
598
+ that._setTime();
599
+ }
600
+
601
+ onChange();
602
+ }
603
+ }
604
+ else {
605
+ // Click anywhere else in the widget - return focus to the
606
+ // input element
607
+ that.dom.input.focus();
608
+ }
609
+ } );
610
+ },
611
+
612
+
613
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
614
+ * Private
615
+ */
616
+
617
+ /**
618
+ * Compare the date part only of two dates - this is made super easy by the
619
+ * toDateString method!
620
+ *
621
+ * @param {Date} a Date 1
622
+ * @param {Date} b Date 2
623
+ * @private
624
+ */
625
+ _compareDates: function( a, b ) {
626
+ // Can't use toDateString as that converts to local time
627
+ // luxon uses different method names so need to be able to call them
628
+ return this._isLuxon()
629
+ ? dateLib.DateTime.fromJSDate(a).toUTC().toISODate() === dateLib.DateTime.fromJSDate(b).toUTC().toISODate()
630
+ : this._dateToUtcString(a) === this._dateToUtcString(b);
631
+ },
632
+
633
+ /**
634
+ * Convert from one format to another
635
+ *
636
+ * @param {string|Date} val Value
637
+ * @param {string|null} from Format to convert from. If null a `Date` must be given
638
+ * @param {string|null} to Format to convert to. If null a `Date` will be returned
639
+ * @returns {string|Date} Converted value
640
+ */
641
+ _convert(val, from, to) {
642
+ if (! val) {
643
+ return val;
644
+ }
645
+
646
+ if (! dateLib) {
647
+ // Note that in here from and to can either be null or YYYY-MM-DD
648
+ // They cannot be anything else
649
+ if ((! from && ! to) || (from && to)) {
650
+ // No conversion
651
+ return val;
652
+ }
653
+ else if (! from) {
654
+ // Date in, string back
655
+ return val.getUTCFullYear() +'-'+
656
+ this._pad(val.getUTCMonth() + 1) +'-'+
657
+ this._pad(val.getUTCDate());
658
+ }
659
+ else { // (! to)
660
+ // String in, date back
661
+ var match = val.match(/(\d{4})\-(\d{2})\-(\d{2})/ );
662
+ return match ?
663
+ new Date( Date.UTC(match[1], match[2]-1, match[3]) ) :
664
+ null;
665
+ }
666
+ }
667
+ else if (this._isLuxon()) {
668
+ // Luxon
669
+ var dtLux = val instanceof Date
670
+ ? dateLib.DateTime.fromJSDate(val).toUTC()
671
+ : dateLib.DateTime.fromFormat(val, from);
672
+
673
+ if (! dtLux.isValid) {
674
+ return null;
675
+ }
676
+
677
+ return to
678
+ ? dtLux.toFormat(to)
679
+ : this._dateToUtc(dtLux.toJSDate());
680
+ }
681
+ else {
682
+ // Moment / DayJS
683
+ var dtMo = val instanceof Date
684
+ ? dateLib.utc( val, undefined, this.c.locale, this.c.strict )
685
+ : dateLib.utc( val, from, this.c.locale, this.c.strict );
686
+
687
+ if (! dtMo.isValid()) {
688
+ return null;
689
+ }
690
+
691
+ return to
692
+ ? dtMo.format(to)
693
+ : dtMo.toDate();
694
+ }
695
+ },
696
+
697
+ /**
698
+ * When changing month, take account of the fact that some months don't have
699
+ * the same number of days. For example going from January to February you
700
+ * can have the 31st of Jan selected and just add a month since the date
701
+ * would still be 31, and thus drop you into March.
702
+ *
703
+ * @param {Date} date Date - will be modified
704
+ * @param {integer} month Month to set
705
+ * @private
706
+ */
707
+ _correctMonth: function ( date, month ) {
708
+ var days = this._daysInMonth( date.getUTCFullYear(), month );
709
+ var correctDays = date.getUTCDate() > days;
710
+
711
+ date.setUTCMonth( month );
712
+
713
+ if ( correctDays ) {
714
+ date.setUTCDate( days );
715
+ date.setUTCMonth( month );
716
+ }
717
+ },
718
+
719
+ /**
720
+ * Get the number of days in a method. Based on
721
+ * http://stackoverflow.com/a/4881951 by Matti Virkkunen
722
+ *
723
+ * @param {integer} year Year
724
+ * @param {integer} month Month (starting at 0)
725
+ * @private
726
+ */
727
+ _daysInMonth: function ( year, month ) {
728
+ //
729
+ var isLeap = ((year % 4) === 0 && ((year % 100) !== 0 || (year % 400) === 0));
730
+ var months = [31, (isLeap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
731
+
732
+ return months[month];
733
+ },
734
+
735
+ /**
736
+ * Create a new date object which has the UTC values set to the local time.
737
+ * This allows the local time to be used directly for the library which
738
+ * always bases its calculations and display on UTC.
739
+ *
740
+ * @param {Date} s Date to "convert"
741
+ * @return {Date} Shifted date
742
+ */
743
+ _dateToUtc: function ( s ) {
744
+ return new Date( Date.UTC(
745
+ s.getFullYear(), s.getMonth(), s.getDate(),
746
+ s.getHours(), s.getMinutes(), s.getSeconds()
747
+ ) );
748
+ },
749
+
750
+ /**
751
+ * Create a UTC ISO8601 date part from a date object
752
+ *
753
+ * @param {Date} d Date to "convert"
754
+ * @return {string} ISO formatted date
755
+ */
756
+ _dateToUtcString: function ( d ) {
757
+ // luxon uses different method names so need to be able to call them
758
+ return this._isLuxon()
759
+ ? dateLib.DateTime.fromJSDate(d).toUTC().toISODate()
760
+ : d.getUTCFullYear()+'-'+
761
+ this._pad(d.getUTCMonth()+1)+'-'+
762
+ this._pad(d.getUTCDate());
763
+ },
764
+
765
+ /**
766
+ * Hide the control and remove events related to its display
767
+ *
768
+ * @private
769
+ */
770
+ _hide: function (destroy) {
771
+ if (! destroy && this.dom.input.attr('type') === 'hidden') {
772
+ return;
773
+ }
774
+
775
+ var namespace = this.s.namespace;
776
+
777
+ this.dom.container.detach();
778
+
779
+ $(window).off( '.'+namespace );
780
+ $(document).off( 'keydown.'+namespace );
781
+ $('div.dataTables_scrollBody').off( 'scroll.'+namespace );
782
+ $('div.DTE_Body_Content').off( 'scroll.'+namespace );
783
+ $('body').off( 'click.'+namespace );
784
+ $(this.dom.input[0].offsetParent).off('.'+namespace);
785
+ },
786
+
787
+ /**
788
+ * Convert a 24 hour value to a 12 hour value
789
+ *
790
+ * @param {integer} val 24 hour value
791
+ * @return {integer} 12 hour value
792
+ * @private
793
+ */
794
+ _hours24To12: function ( val ) {
795
+ return val === 0 ?
796
+ 12 :
797
+ val > 12 ?
798
+ val - 12 :
799
+ val;
800
+ },
801
+
802
+ /**
803
+ * Generate the HTML for a single day in the calendar - this is basically
804
+ * and HTML cell with a button that has data attributes so we know what was
805
+ * clicked on (if it is clicked on) and a bunch of classes for styling.
806
+ *
807
+ * @param {object} day Day object from the `_htmlMonth` method
808
+ * @return {string} HTML cell
809
+ */
810
+ _htmlDay: function( day )
811
+ {
812
+ if ( day.empty ) {
813
+ return '<td class="empty"></td>';
814
+ }
815
+
816
+ var classes = [ 'selectable' ];
817
+ var classPrefix = this.c.classPrefix;
818
+
819
+ if ( day.disabled ) {
820
+ classes.push( 'disabled' );
821
+ }
822
+
823
+ if ( day.today ) {
824
+ classes.push( 'now' );
825
+ }
826
+
827
+ if ( day.selected ) {
828
+ classes.push( 'selected' );
829
+ }
830
+
831
+ return '<td data-day="' + day.day + '" class="' + classes.join(' ') + '">' +
832
+ '<button class="'+classPrefix+'-button '+classPrefix+'-day" type="button" ' +'data-year="' + day.year + '" data-month="' + day.month + '" data-day="' + day.day + '">' +
833
+ '<span>'+day.day+'</span>'+
834
+ '</button>' +
835
+ '</td>';
836
+ },
837
+
838
+
839
+ /**
840
+ * Create the HTML for a month to be displayed in the calendar table.
841
+ *
842
+ * Based upon the logic used in Pikaday - MIT licensed
843
+ * Copyright (c) 2014 David Bushell
844
+ * https://github.com/dbushell/Pikaday
845
+ *
846
+ * @param {integer} year Year
847
+ * @param {integer} month Month (starting at 0)
848
+ * @return {string} Calendar month HTML
849
+ * @private
850
+ */
851
+ _htmlMonth: function ( year, month ) {
852
+ var now = this._dateToUtc( new Date() ),
853
+ days = this._daysInMonth( year, month ),
854
+ before = new Date( Date.UTC(year, month, 1) ).getUTCDay(),
855
+ data = [],
856
+ row = [];
857
+
858
+ if ( this.c.firstDay > 0 ) {
859
+ before -= this.c.firstDay;
860
+
861
+ if (before < 0) {
862
+ before += 7;
863
+ }
864
+ }
865
+
866
+ var cells = days + before,
867
+ after = cells;
868
+
869
+ while ( after > 7 ) {
870
+ after -= 7;
871
+ }
872
+
873
+ cells += 7 - after;
874
+
875
+ var minDate = this.c.minDate;
876
+ var maxDate = this.c.maxDate;
877
+
878
+ if ( minDate ) {
879
+ minDate.setUTCHours(0);
880
+ minDate.setUTCMinutes(0);
881
+ minDate.setSeconds(0);
882
+ }
883
+
884
+ if ( maxDate ) {
885
+ maxDate.setUTCHours(23);
886
+ maxDate.setUTCMinutes(59);
887
+ maxDate.setSeconds(59);
888
+ }
889
+
890
+ for ( var i=0, r=0 ; i<cells ; i++ ) {
891
+ var day = new Date( Date.UTC(year, month, 1 + (i - before)) ),
892
+ selected = this.s.d ? this._compareDates(day, this.s.d) : false,
893
+ today = this._compareDates(day, now),
894
+ empty = i < before || i >= (days + before),
895
+ disabled = (minDate && day < minDate) ||
896
+ (maxDate && day > maxDate);
897
+
898
+ var disableDays = this.c.disableDays;
899
+ if ( Array.isArray( disableDays ) && $.inArray( day.getUTCDay(), disableDays ) !== -1 ) {
900
+ disabled = true;
901
+ }
902
+ else if ( typeof disableDays === 'function' && disableDays( day ) === true ) {
903
+ disabled = true;
904
+ }
905
+
906
+ var dayConfig = {
907
+ day: 1 + (i - before),
908
+ month: month,
909
+ year: year,
910
+ selected: selected,
911
+ today: today,
912
+ disabled: disabled,
913
+ empty: empty
914
+ };
915
+
916
+ row.push( this._htmlDay(dayConfig) );
917
+
918
+ if ( ++r === 7 ) {
919
+ if ( this.c.showWeekNumber ) {
920
+ row.unshift( this._htmlWeekOfYear(i - before, month, year) );
921
+ }
922
+
923
+ data.push( '<tr>'+row.join('')+'</tr>' );
924
+ row = [];
925
+ r = 0;
926
+ }
927
+ }
928
+
929
+ var classPrefix = this.c.classPrefix;
930
+ var className = classPrefix+'-table';
931
+ if ( this.c.showWeekNumber ) {
932
+ className += ' weekNumber';
933
+ }
934
+
935
+ // Show / hide month icons based on min/max
936
+ if ( minDate ) {
937
+ var underMin = minDate >= new Date( Date.UTC(year, month, 1, 0, 0, 0 ) );
938
+
939
+ this.dom.title.find('div.'+classPrefix+'-iconLeft')
940
+ .css( 'display', underMin ? 'none' : 'block' );
941
+ }
942
+
943
+ if ( maxDate ) {
944
+ var overMax = maxDate < new Date( Date.UTC(year, month+1, 1, 0, 0, 0 ) );
945
+
946
+ this.dom.title.find('div.'+classPrefix+'-iconRight')
947
+ .css( 'display', overMax ? 'none' : 'block' );
948
+ }
949
+
950
+ return '<table class="'+className+'">' +
951
+ '<thead>'+
952
+ this._htmlMonthHead() +
953
+ '</thead>'+
954
+ '<tbody>'+
955
+ data.join('') +
956
+ '</tbody>'+
957
+ '</table>';
958
+ },
959
+
960
+ /**
961
+ * Create the calendar table's header (week days)
962
+ *
963
+ * @return {string} HTML cells for the row
964
+ * @private
965
+ */
966
+ _htmlMonthHead: function () {
967
+ var a = [];
968
+ var firstDay = this.c.firstDay;
969
+ var i18n = this.c.i18n;
970
+
971
+ // Take account of the first day shift
972
+ var dayName = function ( day ) {
973
+ day += firstDay;
974
+
975
+ while (day >= 7) {
976
+ day -= 7;
977
+ }
978
+
979
+ return i18n.weekdays[day];
980
+ };
981
+
982
+ // Empty cell in the header
983
+ if ( this.c.showWeekNumber ) {
984
+ a.push( '<th></th>' );
985
+ }
986
+
987
+ for ( var i=0 ; i<7 ; i++ ) {
988
+ a.push( '<th>'+dayName( i )+'</th>' );
989
+ }
990
+
991
+ return a.join('');
992
+ },
993
+
994
+ /**
995
+ * Create a cell that contains week of the year - ISO8601
996
+ *
997
+ * Based on https://stackoverflow.com/questions/6117814/ and
998
+ * http://techblog.procurios.nl/k/n618/news/view/33796/14863/
999
+ *
1000
+ * @param {integer} d Day of month
1001
+ * @param {integer} m Month of year (zero index)
1002
+ * @param {integer} y Year
1003
+ * @return {string}
1004
+ * @private
1005
+ */
1006
+ _htmlWeekOfYear: function ( d, m, y ) {
1007
+ var date = new Date( y, m, d, 0, 0, 0, 0 );
1008
+
1009
+ // First week of the year always has 4th January in it
1010
+ date.setDate( date.getDate() + 4 - (date.getDay() || 7) );
1011
+
1012
+ var oneJan = new Date( y, 0, 1 );
1013
+ var weekNum = Math.ceil( ( ( (date - oneJan) / 86400000) + 1)/7 );
1014
+
1015
+ return '<td class="'+this.c.classPrefix+'-week">' + weekNum + '</td>';
1016
+ },
1017
+
1018
+ /**
1019
+ * Determine if Luxon is being used
1020
+ *
1021
+ * @returns Flag for Luxon
1022
+ */
1023
+ _isLuxon: function () {
1024
+ return dateLib && dateLib.DateTime && dateLib.Duration && dateLib.Settings
1025
+ ? true
1026
+ : false;
1027
+ },
1028
+
1029
+ /**
1030
+ * Check if the instance has a date object value - it might be null.
1031
+ * If is doesn't set one to now.
1032
+ * @returns A Date object
1033
+ * @private
1034
+ */
1035
+ _needValue: function () {
1036
+ if ( ! this.s.d ) {
1037
+ this.s.d = this._dateToUtc( new Date() );
1038
+
1039
+ if (! this.s.parts.time) {
1040
+ this.s.d.setUTCHours(0);
1041
+ this.s.d.setUTCMinutes(0);
1042
+ this.s.d.setSeconds(0);
1043
+ this.s.d.setMilliseconds(0);
1044
+ }
1045
+ }
1046
+
1047
+ return this.s.d;
1048
+ },
1049
+
1050
+ /**
1051
+ * Create option elements from a range in an array
1052
+ *
1053
+ * @param {string} selector Class name unique to the select element to use
1054
+ * @param {array} values Array of values
1055
+ * @param {array} [labels] Array of labels. If given must be the same
1056
+ * length as the values parameter.
1057
+ * @private
1058
+ */
1059
+ _options: function ( selector, values, labels ) {
1060
+ if ( ! labels ) {
1061
+ labels = values;
1062
+ }
1063
+
1064
+ var select = this.dom.container.find('select.'+this.c.classPrefix+'-'+selector);
1065
+ select.empty();
1066
+
1067
+ for ( var i=0, ien=values.length ; i<ien ; i++ ) {
1068
+ select.append( '<option value="'+values[i]+'">'+labels[i]+'</option>' );
1069
+ }
1070
+ },
1071
+
1072
+ /**
1073
+ * Set an option and update the option's span pair (since the select element
1074
+ * has opacity 0 for styling)
1075
+ *
1076
+ * @param {string} selector Class name unique to the select element to use
1077
+ * @param {*} val Value to set
1078
+ * @private
1079
+ */
1080
+ _optionSet: function ( selector, val ) {
1081
+ var select = this.dom.container.find('select.'+this.c.classPrefix+'-'+selector);
1082
+ var span = select.parent().children('span');
1083
+
1084
+ select.val( val );
1085
+
1086
+ var selected = select.find('option:selected');
1087
+ span.html( selected.length !== 0 ?
1088
+ selected.text() :
1089
+ this.c.i18n.unknown
1090
+ );
1091
+ },
1092
+
1093
+ /**
1094
+ * Create time options list.
1095
+ *
1096
+ * @param {string} unit Time unit - hours, minutes or seconds
1097
+ * @param {integer} count Count range - 12, 24 or 60
1098
+ * @param {integer} val Existing value for this unit
1099
+ * @param {integer[]} allowed Values allow for selection
1100
+ * @param {integer} range Override range
1101
+ * @private
1102
+ */
1103
+ _optionsTime: function ( unit, count, val, allowed, range ) {
1104
+ var classPrefix = this.c.classPrefix;
1105
+ var container = this.dom.container.find('div.'+classPrefix+'-'+unit);
1106
+ var i, j;
1107
+ var render = count === 12 ?
1108
+ function (i) { return i; } :
1109
+ this._pad;
1110
+ var classPrefix = this.c.classPrefix;
1111
+ var className = classPrefix+'-table';
1112
+ var i18n = this.c.i18n;
1113
+
1114
+ if ( ! container.length ) {
1115
+ return;
1116
+ }
1117
+
1118
+ var a = '';
1119
+ var span = 10;
1120
+ var button = function (value, label, className) {
1121
+ // Shift the value for PM
1122
+ if ( count === 12 && typeof value === 'number' ) {
1123
+ if (val >= 12 ) {
1124
+ value += 12;
1125
+ }
1126
+
1127
+ if (value == 12) {
1128
+ value = 0;
1129
+ }
1130
+ else if (value == 24) {
1131
+ value = 12;
1132
+ }
1133
+ }
1134
+
1135
+ var selected = val === value || (value === 'am' && val < 12) || (value === 'pm' && val >= 12) ?
1136
+ 'selected' :
1137
+ '';
1138
+
1139
+ if (typeof value === 'number' && allowed && $.inArray(value, allowed) === -1) {
1140
+ selected += ' disabled';
1141
+ }
1142
+
1143
+ if ( className ) {
1144
+ selected += ' '+className;
1145
+ }
1146
+
1147
+ return '<td class="selectable '+selected+'">' +
1148
+ '<button class="'+classPrefix+'-button '+classPrefix+'-day" type="button" data-unit="'+unit+'" data-value="'+value+ '">' +
1149
+ '<span>'+label+'</span>'+
1150
+ '</button>' +
1151
+ '</td>';
1152
+ }
1153
+
1154
+ if ( count === 12 ) {
1155
+ // Hours with AM/PM
1156
+ a += '<tr>';
1157
+
1158
+ for ( i=1 ; i<=6 ; i++ ) {
1159
+ a += button(i, render(i));
1160
+ }
1161
+ a += button('am', i18n.amPm[0]);
1162
+
1163
+ a += '</tr>';
1164
+ a += '<tr>';
1165
+
1166
+ for ( i=7 ; i<=12 ; i++ ) {
1167
+ a += button(i, render(i));
1168
+ }
1169
+ a += button('pm', i18n.amPm[1]);
1170
+ a += '</tr>';
1171
+
1172
+ span = 7;
1173
+ }
1174
+ else if ( count === 24 ) {
1175
+ // Hours - 24
1176
+ var c = 0;
1177
+ for (j=0 ; j<4 ; j++ ) {
1178
+ a += '<tr>';
1179
+ for ( i=0 ; i<6 ; i++ ) {
1180
+ a += button(c, render(c));
1181
+ c++;
1182
+ }
1183
+ a += '</tr>';
1184
+ }
1185
+
1186
+ span = 6;
1187
+ }
1188
+ else {
1189
+ // Minutes and seconds
1190
+ a += '<tr>';
1191
+ for (j=0 ; j<60 ; j+=10 ) {
1192
+ a += button(j, render(j), 'range');
1193
+ }
1194
+ a += '</tr>';
1195
+
1196
+ // Slight hack to allow for the different number of columns
1197
+ a += '</tbody></thead><table class="'+className+' '+className+'-nospace"><tbody>';
1198
+
1199
+ var start = range !== null
1200
+ ? range
1201
+ : val === -1
1202
+ ? 0
1203
+ : Math.floor( val / 10 )*10;
1204
+
1205
+ a += '<tr>';
1206
+ for (j=start+1 ; j<start+10 ; j++ ) {
1207
+ a += button(j, render(j));
1208
+ }
1209
+ a += '</tr>';
1210
+
1211
+ span = 6;
1212
+ }
1213
+
1214
+ container
1215
+ .empty()
1216
+ .append(
1217
+ '<table class="'+className+'">'+
1218
+ '<thead><tr><th colspan="'+span+'">'+
1219
+ i18n[unit] +
1220
+ '</th></tr></thead>'+
1221
+ '<tbody>'+
1222
+ a+
1223
+ '</tbody>'+
1224
+ '</table>'
1225
+ );
1226
+ },
1227
+
1228
+ /**
1229
+ * Create the options for the month and year
1230
+ *
1231
+ * @param {integer} year Year
1232
+ * @param {integer} month Month (starting at 0)
1233
+ * @private
1234
+ */
1235
+ _optionsTitle: function () {
1236
+ var i18n = this.c.i18n;
1237
+ var min = this.c.minDate;
1238
+ var max = this.c.maxDate;
1239
+ var minYear = min ? min.getFullYear() : null;
1240
+ var maxYear = max ? max.getFullYear() : null;
1241
+
1242
+ var i = minYear !== null ? minYear : new Date().getFullYear() - this.c.yearRange;
1243
+ var j = maxYear !== null ? maxYear : new Date().getFullYear() + this.c.yearRange;
1244
+
1245
+ this._options( 'month', this._range( 0, 11 ), i18n.months );
1246
+ this._options( 'year', this._range( i, j ) );
1247
+
1248
+ // Set the language strings in case any have changed
1249
+ this.dom.today.text(i18n.today).text(i18n.today);
1250
+ this.dom.clear.text(i18n.clear).text(i18n.clear);
1251
+ this.dom.previous
1252
+ .attr('title', i18n.previous)
1253
+ .children('button')
1254
+ .text(i18n.previous);
1255
+ this.dom.next
1256
+ .attr('title', i18n.next)
1257
+ .children('button')
1258
+ .text(i18n.next);
1259
+ },
1260
+
1261
+ /**
1262
+ * Simple two digit pad
1263
+ *
1264
+ * @param {integer} i Value that might need padding
1265
+ * @return {string|integer} Padded value
1266
+ * @private
1267
+ */
1268
+ _pad: function ( i ) {
1269
+ return i<10 ? '0'+i : i;
1270
+ },
1271
+
1272
+ /**
1273
+ * Position the calendar to look attached to the input element
1274
+ * @private
1275
+ */
1276
+ _position: function () {
1277
+ var offset = this.c.attachTo === 'input' ? this.dom.input.position() : this.dom.input.offset();
1278
+ var container = this.dom.container;
1279
+ var inputHeight = this.dom.input.outerHeight();
1280
+
1281
+ if (container.hasClass('inline')) {
1282
+ container.insertAfter( this.dom.input );
1283
+ return;
1284
+ }
1285
+
1286
+ if ( this.s.parts.date && this.s.parts.time && $(window).width() > 550 ) {
1287
+ container.addClass('horizontal');
1288
+ }
1289
+ else {
1290
+ container.removeClass('horizontal');
1291
+ }
1292
+
1293
+ if(this.c.attachTo === 'input') {
1294
+ container
1295
+ .css( {
1296
+ top: offset.top + inputHeight,
1297
+ left: offset.left
1298
+ } )
1299
+ .insertAfter( this.dom.input );
1300
+ }
1301
+ else {
1302
+ container
1303
+ .css( {
1304
+ top: offset.top + inputHeight,
1305
+ left: offset.left
1306
+ } )
1307
+ .appendTo( 'body' );
1308
+ }
1309
+
1310
+ var calHeight = container.outerHeight();
1311
+ var calWidth = container.outerWidth();
1312
+ var scrollTop = $(window).scrollTop();
1313
+
1314
+ // Correct to the bottom
1315
+ if ( offset.top + inputHeight + calHeight - scrollTop > $(window).height() ) {
1316
+ var newTop = offset.top - calHeight;
1317
+
1318
+ container.css( 'top', newTop < 0 ? 0 : newTop );
1319
+ }
1320
+
1321
+ // Correct to the right
1322
+ if ( calWidth + offset.left > $(window).width() ) {
1323
+ var newLeft = $(window).width() - calWidth;
1324
+
1325
+ // Account for elements which are inside a position absolute element
1326
+ if (this.c.attachTo === 'input') {
1327
+ newLeft -= $(container).offsetParent().offset().left;
1328
+ }
1329
+
1330
+ container.css( 'left', newLeft < 0 ? 0 : newLeft );
1331
+ }
1332
+ },
1333
+
1334
+ /**
1335
+ * Create a simple array with a range of values
1336
+ *
1337
+ * @param {integer} start Start value (inclusive)
1338
+ * @param {integer} end End value (inclusive)
1339
+ * @param {integer} [inc=1] Increment value
1340
+ * @return {array} Created array
1341
+ * @private
1342
+ */
1343
+ _range: function ( start, end, inc ) {
1344
+ var a = [];
1345
+
1346
+ if ( ! inc ) {
1347
+ inc = 1;
1348
+ }
1349
+
1350
+ for ( var i=start ; i<=end ; i+=inc ) {
1351
+ a.push( i );
1352
+ }
1353
+
1354
+ return a;
1355
+ },
1356
+
1357
+ /**
1358
+ * Redraw the calendar based on the display date - this is a destructive
1359
+ * operation
1360
+ *
1361
+ * @private
1362
+ */
1363
+ _setCalander: function () {
1364
+ if ( this.s.display ) {
1365
+ this.dom.calendar
1366
+ .empty()
1367
+ .append( this._htmlMonth(
1368
+ this.s.display.getUTCFullYear(),
1369
+ this.s.display.getUTCMonth()
1370
+ ) );
1371
+ }
1372
+ },
1373
+
1374
+ /**
1375
+ * Set the month and year for the calendar based on the current display date
1376
+ *
1377
+ * @private
1378
+ */
1379
+ _setTitle: function () {
1380
+ this._optionSet( 'month', this.s.display.getUTCMonth() );
1381
+ this._optionSet( 'year', this.s.display.getUTCFullYear() );
1382
+ },
1383
+
1384
+ /**
1385
+ * Set the time based on the current value of the widget
1386
+ *
1387
+ * @private
1388
+ */
1389
+ _setTime: function () {
1390
+ var that = this;
1391
+ var d = this.s.d;
1392
+
1393
+ // luxon uses different method names so need to be able to call them. This happens a few time later in this method too
1394
+ var luxDT = null
1395
+ if (this._isLuxon()) {
1396
+ luxDT = dateLib.DateTime.fromJSDate(d).toUTC();
1397
+ }
1398
+
1399
+ var hours = luxDT != null
1400
+ ? luxDT.hour
1401
+ : d
1402
+ ? d.getUTCHours()
1403
+ : -1;
1404
+
1405
+ var allowed = function ( prop ) { // Backwards compt with `Increment` option
1406
+ return that.c[prop+'Available'] ?
1407
+ that.c[prop+'Available'] :
1408
+ that._range( 0, 59, that.c[prop+'Increment'] );
1409
+ }
1410
+
1411
+ this._optionsTime( 'hours', this.s.parts.hours12 ? 12 : 24, hours, this.c.hoursAvailable )
1412
+ this._optionsTime(
1413
+ 'minutes',
1414
+ 60,
1415
+ luxDT != null
1416
+ ? luxDT.minute
1417
+ : d
1418
+ ? d.getUTCMinutes()
1419
+ : -1,
1420
+ allowed('minutes'),
1421
+ this.s.minutesRange
1422
+ );
1423
+ this._optionsTime(
1424
+ 'seconds',
1425
+ 60,
1426
+ luxDT != null
1427
+ ? luxDT.second
1428
+ : d
1429
+ ? d.getSeconds()
1430
+ : -1,
1431
+ allowed('seconds'),
1432
+ this.s.secondsRange
1433
+ );
1434
+ },
1435
+
1436
+ /**
1437
+ * Show the widget and add events to the document required only while it
1438
+ * is displayed
1439
+ *
1440
+ * @private
1441
+ */
1442
+ _show: function () {
1443
+ var that = this;
1444
+ var namespace = this.s.namespace;
1445
+
1446
+ this._position();
1447
+
1448
+ // Need to reposition on scroll
1449
+ $(window).on( 'scroll.'+namespace+' resize.'+namespace, function () {
1450
+ that._position();
1451
+ } );
1452
+
1453
+ $('div.DTE_Body_Content').on( 'scroll.'+namespace, function () {
1454
+ that._position();
1455
+ } );
1456
+
1457
+ $('div.dataTables_scrollBody').on( 'scroll.'+namespace, function () {
1458
+ that._position();
1459
+ } );
1460
+
1461
+ var offsetParent = this.dom.input[0].offsetParent;
1462
+
1463
+ if ( offsetParent !== document.body ) {
1464
+ $(offsetParent).on( 'scroll.'+namespace, function () {
1465
+ that._position();
1466
+ } );
1467
+ }
1468
+
1469
+ // On tab focus will move to a different field (no keyboard navigation
1470
+ // in the date picker - this might need to be changed).
1471
+ $(document).on( 'keydown.'+namespace, function (e) {
1472
+ if (
1473
+ e.keyCode === 9 || // tab
1474
+ e.keyCode === 27 || // esc
1475
+ e.keyCode === 13 // return
1476
+ ) {
1477
+ that._hide();
1478
+ }
1479
+ } );
1480
+
1481
+ // Hide if clicking outside of the widget - but in a different click
1482
+ // event from the one that was used to trigger the show (bubble and
1483
+ // inline)
1484
+ setTimeout( function () {
1485
+ $('body').on( 'click.'+namespace, function (e) {
1486
+ var parents = $(e.target).parents();
1487
+
1488
+ if ( ! parents.filter( that.dom.container ).length && e.target !== that.dom.input[0] ) {
1489
+ that._hide();
1490
+ }
1491
+ } );
1492
+ }, 10 );
1493
+ },
1494
+
1495
+ /**
1496
+ * Write the formatted string to the input element this control is attached
1497
+ * to
1498
+ *
1499
+ * @private
1500
+ */
1501
+ _writeOutput: function ( focus ) {
1502
+ var date = this.s.d;
1503
+ var out = '';
1504
+
1505
+ if (date) {
1506
+ out = this._convert(date, null, this.c.format);
1507
+ }
1508
+
1509
+ this.dom.input
1510
+ .val( out )
1511
+ .trigger('change', {write: date});
1512
+
1513
+ if ( this.dom.input.attr('type') === 'hidden' ) {
1514
+ this.val(out, false);
1515
+ }
1516
+
1517
+ if ( focus ) {
1518
+ this.dom.input.focus();
1519
+ }
1520
+ }
1521
+ } );
1522
+
1523
+ /**
1524
+ * Use a specificmoment compatible date library
1525
+ */
1526
+ DateTime.use = function (lib) {
1527
+ dateLib = lib;
1528
+ };
1529
+
1530
+ /**
1531
+ * For generating unique namespaces
1532
+ *
1533
+ * @type {Number}
1534
+ * @private
1535
+ */
1536
+ DateTime._instance = 0;
1537
+
1538
+ /**
1539
+ * Defaults for the date time picker
1540
+ *
1541
+ * @type {Object}
1542
+ */
1543
+ DateTime.defaults = {
1544
+ attachTo: 'body',
1545
+
1546
+ buttons: {
1547
+ clear: false,
1548
+ today: false
1549
+ },
1550
+
1551
+ // Not documented - could be an internal property
1552
+ classPrefix: 'dt-datetime',
1553
+
1554
+ // function or array of ints
1555
+ disableDays: null,
1556
+
1557
+ // first day of the week (0: Sunday, 1: Monday, etc)
1558
+ firstDay: 1,
1559
+
1560
+ format: 'YYYY-MM-DD',
1561
+
1562
+ hoursAvailable: null,
1563
+
1564
+ i18n: {
1565
+ clear: 'Clear',
1566
+ previous: 'Previous',
1567
+ next: 'Next',
1568
+ months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
1569
+ weekdays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
1570
+ amPm: [ 'am', 'pm' ],
1571
+ hours: 'Hour',
1572
+ minutes: 'Minute',
1573
+ seconds: 'Second',
1574
+ unknown: '-',
1575
+ today: 'Today'
1576
+ },
1577
+
1578
+ maxDate: null,
1579
+
1580
+ minDate: null,
1581
+
1582
+ minutesAvailable: null,
1583
+
1584
+ minutesIncrement: 1, // deprecated
1585
+
1586
+ strict: true,
1587
+
1588
+ locale: 'en',
1589
+
1590
+ onChange: function () {},
1591
+
1592
+ secondsAvailable: null,
1593
+
1594
+ secondsIncrement: 1, // deprecated
1595
+
1596
+ // show the ISO week number at the head of the row
1597
+ showWeekNumber: false,
1598
+
1599
+ // overruled by max / min date
1600
+ yearRange: 25
1601
+ };
1602
+
1603
+ DateTime.version = '1.3.0';
1604
+
1605
+ // Global export - if no conflicts
1606
+ if (! window.DateTime) {
1607
+ window.DateTime = DateTime;
1608
+ }
1609
+
1610
+ // Make available via jQuery
1611
+ $.fn.dtDateTime = function (options) {
1612
+ return this.each(function() {
1613
+ new DateTime(this, options);
1614
+ });
1615
+ }
1616
+
1617
+ // Attach to DataTables if present
1618
+ if ($.fn.dataTable) {
1619
+ $.fn.dataTable.DateTime = DateTime;
1620
+ $.fn.DataTable.DateTime = DateTime;
1621
+
1622
+ if ($.fn.dataTable.Editor) {
1623
+ $.fn.dataTable.Editor.DateTime = DateTime;
1624
+ }
1625
+ }
1626
+
1627
+
1628
+ export default DateTime;