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