datatables.net-datetime 1.6.3 → 2.0.0-beta.1

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