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