itskp-odense/Frontend/Assets/DataTables/DateTime-1.1.0/js/dataTables.dateTime.js

1564 lines
38 KiB
JavaScript
Raw Normal View History

/*! DateTime picker for DataTables.net v1.1.0
*
* © SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
/**
* @summary DateTime picker for DataTables.net
* @version 1.1.0
* @file dataTables.dateTime.js
* @author SpryMedia Ltd
* @contact www.datatables.net/contact
*/
(function( factory ){
if ( typeof define === 'function' && define.amd ) {
// AMD
define( ['jquery'], function ( $ ) {
return factory( $, window, document );
} );
}
else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = function (root, $) {
if ( ! root ) {
root = window;
}
return factory( $, root, root.document );
};
}
else {
// Browser
factory( jQuery, window, document );
}
}(function( $, window, document, undefined ) {
'use strict';
// Supported formatting and parsing libraries:
// * Moment
// * Luxon
// * DayJS
var dateLib;
/*
* This file provides a DateTime GUI picker (calendar and time input). Only the
* format YYYY-MM-DD is supported without additional software, but the end user
* experience can be greatly enhanced by including the momentjs, dayjs or luxon library
* which provide date / time parsing and formatting options.
*
* This functionality is required because the HTML5 date and datetime input
* types are not widely supported in desktop browsers.
*
* Constructed by using:
*
* new DateTime( input, opts )
*
* where `input` is the HTML input element to use and `opts` is an object of
* options based on the `DateTime.defaults` object.
*/
var DateTime = function ( input, opts ) {
// Attempt to auto detect the formatting library (if there is one). Having it in
// the constructor allows load order independence.
if (typeof dateLib === 'undefined') {
dateLib = window.moment
? window.moment
: window.dayjs
? window.dayjs
: window.luxon
? window.luxon
: null;
}
this.c = $.extend( true, {}, DateTime.defaults, opts );
var classPrefix = this.c.classPrefix;
var i18n = this.c.i18n;
// Only IS8601 dates are supported without moment, dayjs or luxon
if ( ! dateLib && this.c.format !== 'YYYY-MM-DD' ) {
throw "DateTime: Without momentjs, dayjs or luxon only the format 'YYYY-MM-DD' can be used";
}
// Min and max need to be `Date` objects in the config
if (typeof this.c.minDate === 'string') {
this.c.minDate = new Date(this.c.minDate);
}
if (typeof this.c.maxDate === 'string') {
this.c.maxDate = new Date(this.c.maxDate);
}
var timeBlock = function ( type ) {
return '<div class="'+classPrefix+'-timeblock">'+
'</div>';
};
var gap = function () {
return '<span>:</span>';
};
// DOM structure
var structure = $(
'<div class="'+classPrefix+'">'+
'<div class="'+classPrefix+'-date">'+
'<div class="'+classPrefix+'-title">'+
'<div class="'+classPrefix+'-iconLeft">'+
'<button title="'+i18n.previous+'">'+i18n.previous+'</button>'+
'</div>'+
'<div class="'+classPrefix+'-iconRight">'+
'<button title="'+i18n.next+'">'+i18n.next+'</button>'+
'</div>'+
'<div class="'+classPrefix+'-label">'+
'<span></span>'+
'<select class="'+classPrefix+'-month"></select>'+
'</div>'+
'<div class="'+classPrefix+'-label">'+
'<span></span>'+
'<select class="'+classPrefix+'-year"></select>'+
'</div>'+
'</div>'+
'<div class="'+classPrefix+'-buttons">'+
'<a class="'+classPrefix+'-clear">'+i18n.clear+'</a>'+
'<a class="'+classPrefix+'-today">'+i18n.today+'</a>'+
'</div>'+
'<div class="'+classPrefix+'-calendar"></div>'+
'</div>'+
'<div class="'+classPrefix+'-time">'+
'<div class="'+classPrefix+'-hours"></div>'+
'<div class="'+classPrefix+'-minutes"></div>'+
'<div class="'+classPrefix+'-seconds"></div>'+
'</div>'+
'<div class="'+classPrefix+'-error"></div>'+
'</div>'
);
this.dom = {
container: structure,
date: structure.find( '.'+classPrefix+'-date' ),
title: structure.find( '.'+classPrefix+'-title' ),
calendar: structure.find( '.'+classPrefix+'-calendar' ),
time: structure.find( '.'+classPrefix+'-time' ),
error: structure.find( '.'+classPrefix+'-error' ),
buttons: structure.find( '.'+classPrefix+'-buttons' ),
clear: structure.find( '.'+classPrefix+'-clear' ),
today: structure.find( '.'+classPrefix+'-today' ),
input: $(input)
};
this.s = {
/** @type {Date} Date value that the picker has currently selected */
d: null,
/** @type {Date} Date of the calendar - might not match the value */
display: null,
/** @type {number} Used to select minutes in a range where the range base is itself unavailable */
minutesRange: null,
/** @type {number} Used to select minutes in a range where the range base is itself unavailable */
secondsRange: null,
/** @type {String} Unique namespace string for this instance */
namespace: 'dateime-'+(DateTime._instance++),
/** @type {Object} Parts of the picker that should be shown */
parts: {
date: this.c.format.match( /[YMD]|L(?!T)|l/ ) !== null,
time: this.c.format.match( /[Hhm]|LT|LTS/ ) !== null,
seconds: this.c.format.indexOf( 's' ) !== -1,
hours12: this.c.format.match( /[haA]/ ) !== null
}
};
this.dom.container
.append( this.dom.date )
.append( this.dom.time )
.append( this.dom.error );
this.dom.date
.append( this.dom.title )
.append( this.dom.buttons )
.append( this.dom.calendar );
this._constructor();
};
$.extend( DateTime.prototype, {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Public
*/
/**
* Destroy the control
*/
destroy: function () {
this._hide(true);
this.dom.container.off().empty();
this.dom.input
.removeAttr('autocomplete')
.off('.datetime');
},
errorMsg: function ( msg ) {
var error = this.dom.error;
if ( msg ) {
error.html( msg );
}
else {
error.empty();
}
return this;
},
hide: function () {
this._hide();
return this;
},
max: function ( date ) {
this.c.maxDate = typeof date === 'string'
? new Date(date)
: date;
this._optionsTitle();
this._setCalander();
return this;
},
min: function ( date ) {
this.c.minDate = typeof date === 'string'
? new Date(date)
: date;
this._optionsTitle();
this._setCalander();
return this;
},
/**
* Check if an element belongs to this control
*
* @param {node} node Element to check
* @return {boolean} true if owned by this control, false otherwise
*/
owns: function ( node ) {
return $(node).parents().filter( this.dom.container ).length > 0;
},
/**
* Get / set the value
*
* @param {string|Date} set Value to set
* @param {boolean} [write=true] Flag to indicate if the formatted value
* should be written into the input element
*/
val: function ( set, write ) {
if ( set === undefined ) {
return this.s.d;
}
if ( set instanceof Date ) {
this.s.d = this._dateToUtc( set );
}
else if ( set === null || set === '' ) {
this.s.d = null;
}
else if ( set === '--now' ) {
this.s.d = new Date();
}
else if ( typeof set === 'string' ) {
// luxon uses different method names so need to be able to call them
if(dateLib && dateLib == window.luxon) {
var luxDT = dateLib.DateTime.fromFormat(set, this.c.format)
this.s.d = luxDT.isValid ? luxDT.toJSDate() : null;
}
else if ( dateLib ) {
// Use moment, dayjs or luxon if possible (even for ISO8601 strings, since it
// will correctly handle 0000-00-00 and the like)
var m = dateLib.utc( set, this.c.format, this.c.locale, this.c.strict );
this.s.d = m.isValid() ? m.toDate() : null;
}
else {
// Else must be using ISO8601 without a date library (constructor would
// have thrown an error otherwise)
var match = set.match(/(\d{4})\-(\d{2})\-(\d{2})/ );
this.s.d = match ?
new Date( Date.UTC(match[1], match[2]-1, match[3]) ) :
null;
}
}
if ( write || write === undefined ) {
if ( this.s.d ) {
this._writeOutput();
}
else {
// The input value was not valid...
this.dom.input.val( set );
}
}
// Need something to display
this.s.display = this.s.d
? new Date( this.s.d.toString() )
: new Date();
// Set the day of the month to be 1 so changing between months doesn't
// run into issues when going from day 31 to 28 (for example)
this.s.display.setUTCDate( 1 );
// Update the display elements for the new value
this._setTitle();
this._setCalander();
this._setTime();
return this;
},
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Constructor
*/
/**
* Build the control and assign initial event handlers
*
* @private
*/
_constructor: function () {
var that = this;
var classPrefix = this.c.classPrefix;
var last = this.dom.input.val();
var onChange = function () {
var curr = that.dom.input.val();
if (curr !== last) {
that.c.onChange.call( that, curr, that.s.d, that.dom.input );
last = curr;
}
};
if ( ! this.s.parts.date ) {
this.dom.date.css( 'display', 'none' );
}
if ( ! this.s.parts.time ) {
this.dom.time.css( 'display', 'none' );
}
if ( ! this.s.parts.seconds ) {
this.dom.time.children('div.'+classPrefix+'-seconds').remove();
this.dom.time.children('span').eq(1).remove();
}
if ( ! this.c.buttons.clear ) {
this.dom.clear.css( 'display', 'none' );
}
if ( ! this.c.buttons.today ) {
this.dom.today.css( 'display', 'none' );
}
// Render the options
this._optionsTitle();
$(document).on('i18n.dt', function (e, settings) {
if (settings.oLanguage.datetime) {
$.extend(true, that.c.i18n, settings.oLanguage.datetime);
this._optionsTitle();
}
});
// When attached to a hidden input, we always show the input picker, and
// do so inline
if (this.dom.input.attr('type') === 'hidden') {
this.dom.container.addClass('inline');
this.c.attachTo = 'input';
this.val( this.dom.input.val(), false );
this._show();
}
// Set the initial value
if (last) {
this.val( last, false );
}
// Trigger the display of the widget when clicking or focusing on the
// input element
this.dom.input
.attr('autocomplete', 'off')
.on('focus.datetime click.datetime', function () {
// If already visible - don't do anything
if ( that.dom.container.is(':visible') || that.dom.input.is(':disabled') ) {
return;
}
// In case the value has changed by text
that.val( that.dom.input.val(), false );
that._show();
} )
.on('keyup.datetime', function () {
// Update the calendar's displayed value as the user types
if ( that.dom.container.is(':visible') ) {
that.val( that.dom.input.val(), false );
}
} );
// Main event handlers for input in the widget
this.dom.container
.on( 'change', 'select', function () {
var select = $(this);
var val = select.val();
if ( select.hasClass(classPrefix+'-month') ) {
// Month select
that._correctMonth( that.s.display, val );
that._setTitle();
that._setCalander();
}
else if ( select.hasClass(classPrefix+'-year') ) {
// Year select
that.s.display.setUTCFullYear( val );
that._setTitle();
that._setCalander();
}
else if ( select.hasClass(classPrefix+'-hours') || select.hasClass(classPrefix+'-ampm') ) {
// Hours - need to take account of AM/PM input if present
if ( that.s.parts.hours12 ) {
var hours = $(that.dom.container).find('.'+classPrefix+'-hours').val() * 1;
var pm = $(that.dom.container).find('.'+classPrefix+'-ampm').val() === 'pm';
that.s.d.setUTCHours( hours === 12 && !pm ?
0 :
pm && hours !== 12 ?
hours + 12 :
hours
);
}
else {
that.s.d.setUTCHours( val );
}
that._setTime();
that._writeOutput( true );
onChange();
}
else if ( select.hasClass(classPrefix+'-minutes') ) {
// Minutes select
that.s.d.setUTCMinutes( val );
that._setTime();
that._writeOutput( true );
onChange();
}
else if ( select.hasClass(classPrefix+'-seconds') ) {
// Seconds select
that.s.d.setSeconds( val );
that._setTime();
that._writeOutput( true );
onChange();
}
that.dom.input.focus();
that._position();
} )
.on( 'click', function (e) {
var d = that.s.d;
var nodeName = e.target.nodeName.toLowerCase();
var target = nodeName === 'span' ?
e.target.parentNode :
e.target;
nodeName = target.nodeName.toLowerCase();
if ( nodeName === 'select' ) {
return;
}
e.stopPropagation();
if ( nodeName === 'a' ) {
e.preventDefault();
if ($(target).hasClass(classPrefix+'-clear')) {
// Clear the value and don't change the display
that.s.d = null;
that.dom.input.val('');
that._writeOutput();
that._setCalander();
that._setTime();
onChange();
}
else if ($(target).hasClass(classPrefix+'-today')) {
// Don't change the value, but jump to the month
// containing today
that.s.display = new Date();
that._setTitle();
that._setCalander();
}
}
if ( nodeName === 'button' ) {
var button = $(target);
var parent = button.parent();
if ( parent.hasClass('disabled') && ! parent.hasClass('range') ) {
button.blur();
return;
}
if ( parent.hasClass(classPrefix+'-iconLeft') ) {
// Previous month
that.s.display.setUTCMonth( that.s.display.getUTCMonth()-1 );
that._setTitle();
that._setCalander();
that.dom.input.focus();
}
else if ( parent.hasClass(classPrefix+'-iconRight') ) {
// Next month
that._correctMonth( that.s.display, that.s.display.getUTCMonth()+1 );
that._setTitle();
that._setCalander();
that.dom.input.focus();
}
else if ( button.parents('.'+classPrefix+'-time').length ) {
var val = button.data('value');
var unit = button.data('unit');
d = that._needValue();
if ( unit === 'minutes' ) {
if ( parent.hasClass('disabled') && parent.hasClass('range') ) {
that.s.minutesRange = val;
that._setTime();
return;
}
else {
that.s.minutesRange = null;
}
}
if ( unit === 'seconds' ) {
if ( parent.hasClass('disabled') && parent.hasClass('range') ) {
that.s.secondsRange = val;
that._setTime();
return;
}
else {
that.s.secondsRange = null;
}
}
// Specific to hours for 12h clock
if ( val === 'am' ) {
if ( d.getUTCHours() >= 12 ) {
val = d.getUTCHours() - 12;
}
else {
return;
}
}
else if ( val === 'pm' ) {
if ( d.getUTCHours() < 12 ) {
val = d.getUTCHours() + 12;
}
else {
return;
}
}
var set = unit === 'hours' ?
'setUTCHours' :
unit === 'minutes' ?
'setUTCMinutes' :
'setSeconds';
d[set]( val );
that._setTime();
that._writeOutput( true );
onChange();
}
else {
// Calendar click
d = that._needValue();
// Can't be certain that the current day will exist in
// the new month, and likewise don't know that the
// new day will exist in the old month, But 1 always
// does, so we can change the month without worry of a
// recalculation being done automatically by `Date`
d.setUTCDate( 1 );
d.setUTCFullYear( button.data('year') );
d.setUTCMonth( button.data('month') );
d.setUTCDate( button.data('day') );
that._writeOutput( true );
// Don't hide if there is a time picker, since we want to
// be able to select a time as well.
if ( ! that.s.parts.time ) {
// This is annoying but IE has some kind of async
// behaviour with focus and the focus from the above
// write would occur after this hide - resulting in the
// calendar opening immediately
setTimeout( function () {
that._hide();
}, 10 );
}
else {
that._setCalander();
}
onChange();
}
}
else {
// Click anywhere else in the widget - return focus to the
// input element
that.dom.input.focus();
}
} );
},
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private
*/
/**
* Compare the date part only of two dates - this is made super easy by the
* toDateString method!
*
* @param {Date} a Date 1
* @param {Date} b Date 2
* @private
*/
_compareDates: function( a, b ) {
// Can't use toDateString as that converts to local time
// luxon uses different method names so need to be able to call them
return dateLib && dateLib == window.luxon
? dateLib.DateTime.fromJSDate(a).toISODate() === dateLib.DateTime.fromJSDate(b).toISODate()
: this._dateToUtcString(a) === this._dateToUtcString(b);
},
/**
* When changing month, take account of the fact that some months don't have
* the same number of days. For example going from January to February you
* can have the 31st of Jan selected and just add a month since the date
* would still be 31, and thus drop you into March.
*
* @param {Date} date Date - will be modified
* @param {integer} month Month to set
* @private
*/
_correctMonth: function ( date, month ) {
var days = this._daysInMonth( date.getUTCFullYear(), month );
var correctDays = date.getUTCDate() > days;
date.setUTCMonth( month );
if ( correctDays ) {
date.setUTCDate( days );
date.setUTCMonth( month );
}
},
/**
* Get the number of days in a method. Based on
* http://stackoverflow.com/a/4881951 by Matti Virkkunen
*
* @param {integer} year Year
* @param {integer} month Month (starting at 0)
* @private
*/
_daysInMonth: function ( year, month ) {
//
var isLeap = ((year % 4) === 0 && ((year % 100) !== 0 || (year % 400) === 0));
var months = [31, (isLeap ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return months[month];
},
/**
* Create a new date object which has the UTC values set to the local time.
* This allows the local time to be used directly for the library which
* always bases its calculations and display on UTC.
*
* @param {Date} s Date to "convert"
* @return {Date} Shifted date
*/
_dateToUtc: function ( s ) {
return new Date( Date.UTC(
s.getFullYear(), s.getMonth(), s.getDate(),
s.getHours(), s.getMinutes(), s.getSeconds()
) );
},
/**
* Create a UTC ISO8601 date part from a date object
*
* @param {Date} d Date to "convert"
* @return {string} ISO formatted date
*/
_dateToUtcString: function ( d ) {
// luxon uses different method names so need to be able to call them
return dateLib && dateLib == window.luxon
? dateLib.DateTime.fromJSDate(d).toISODate()
: d.getUTCFullYear()+'-'+
this._pad(d.getUTCMonth()+1)+'-'+
this._pad(d.getUTCDate());
},
/**
* Hide the control and remove events related to its display
*
* @private
*/
_hide: function (destroy) {
if (! destroy && this.dom.input.attr('type') === 'hidden') {
return;
}
var namespace = this.s.namespace;
this.dom.container.detach();
$(window).off( '.'+namespace );
$(document).off( 'keydown.'+namespace );
$('div.dataTables_scrollBody').off( 'scroll.'+namespace );
$('div.DTE_Body_Content').off( 'scroll.'+namespace );
$('body').off( 'click.'+namespace );
},
/**
* Convert a 24 hour value to a 12 hour value
*
* @param {integer} val 24 hour value
* @return {integer} 12 hour value
* @private
*/
_hours24To12: function ( val ) {
return val === 0 ?
12 :
val > 12 ?
val - 12 :
val;
},
/**
* Generate the HTML for a single day in the calendar - this is basically
* and HTML cell with a button that has data attributes so we know what was
* clicked on (if it is clicked on) and a bunch of classes for styling.
*
* @param {object} day Day object from the `_htmlMonth` method
* @return {string} HTML cell
*/
_htmlDay: function( day )
{
if ( day.empty ) {
return '<td class="empty"></td>';
}
var classes = [ 'selectable' ];
var classPrefix = this.c.classPrefix;
if ( day.disabled ) {
classes.push( 'disabled' );
}
if ( day.today ) {
classes.push( 'now' );
}
if ( day.selected ) {
classes.push( 'selected' );
}
return '<td data-day="' + day.day + '" class="' + classes.join(' ') + '">' +
'<button class="'+classPrefix+'-button '+classPrefix+'-day" type="button" ' +'data-year="' + day.year + '" data-month="' + day.month + '" data-day="' + day.day + '">' +
'<span>'+day.day+'</span>'+
'</button>' +
'</td>';
},
/**
* Create the HTML for a month to be displayed in the calendar table.
*
* Based upon the logic used in Pikaday - MIT licensed
* Copyright (c) 2014 David Bushell
* https://github.com/dbushell/Pikaday
*
* @param {integer} year Year
* @param {integer} month Month (starting at 0)
* @return {string} Calendar month HTML
* @private
*/
_htmlMonth: function ( year, month ) {
var now = this._dateToUtc( new Date() ),
days = this._daysInMonth( year, month ),
before = new Date( Date.UTC(year, month, 1) ).getUTCDay(),
data = [],
row = [];
if ( this.c.firstDay > 0 ) {
before -= this.c.firstDay;
if (before < 0) {
before += 7;
}
}
var cells = days + before,
after = cells;
while ( after > 7 ) {
after -= 7;
}
cells += 7 - after;
var minDate = this.c.minDate;
var maxDate = this.c.maxDate;
if ( minDate ) {
minDate.setUTCHours(0);
minDate.setUTCMinutes(0);
minDate.setSeconds(0);
}
if ( maxDate ) {
maxDate.setUTCHours(23);
maxDate.setUTCMinutes(59);
maxDate.setSeconds(59);
}
for ( var i=0, r=0 ; i<cells ; i++ ) {
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);
var disableDays = this.c.disableDays;
if ( Array.isArray( disableDays ) && $.inArray( day.getUTCDay(), disableDays ) !== -1 ) {
disabled = true;
}
else if ( typeof disableDays === 'function' && disableDays( day ) === true ) {
disabled = true;
}
var dayConfig = {
day: 1 + (i - before),
month: month,
year: year,
selected: selected,
today: today,
disabled: disabled,
empty: empty
};
row.push( this._htmlDay(dayConfig) );
if ( ++r === 7 ) {
if ( this.c.showWeekNumber ) {
row.unshift( this._htmlWeekOfYear(i - before, month, year) );
}
data.push( '<tr>'+row.join('')+'</tr>' );
row = [];
r = 0;
}
}
var classPrefix = this.c.classPrefix;
var className = classPrefix+'-table';
if ( this.c.showWeekNumber ) {
className += ' weekNumber';
}
// Show / hide month icons based on min/max
if ( minDate ) {
var underMin = minDate >= new Date( Date.UTC(year, month, 1, 0, 0, 0 ) );
this.dom.title.find('div.'+classPrefix+'-iconLeft')
.css( 'display', underMin ? 'none' : 'block' );
}
if ( maxDate ) {
var overMax = maxDate < new Date( Date.UTC(year, month+1, 1, 0, 0, 0 ) );
this.dom.title.find('div.'+classPrefix+'-iconRight')
.css( 'display', overMax ? 'none' : 'block' );
}
return '<table class="'+className+'">' +
'<thead>'+
this._htmlMonthHead() +
'</thead>'+
'<tbody>'+
data.join('') +
'</tbody>'+
'</table>';
},
/**
* Create the calendar table's header (week days)
*
* @return {string} HTML cells for the row
* @private
*/
_htmlMonthHead: function () {
var a = [];
var firstDay = this.c.firstDay;
var i18n = this.c.i18n;
// Take account of the first day shift
var dayName = function ( day ) {
day += firstDay;
while (day >= 7) {
day -= 7;
}
return i18n.weekdays[day];
};
// Empty cell in the header
if ( this.c.showWeekNumber ) {
a.push( '<th></th>' );
}
for ( var i=0 ; i<7 ; i++ ) {
a.push( '<th>'+dayName( i )+'</th>' );
}
return a.join('');
},
/**
* Create a cell that contains week of the year - ISO8601
*
* Based on https://stackoverflow.com/questions/6117814/ and
* http://techblog.procurios.nl/k/n618/news/view/33796/14863/
*
* @param {integer} d Day of month
* @param {integer} m Month of year (zero index)
* @param {integer} y Year
* @return {string}
* @private
*/
_htmlWeekOfYear: function ( d, m, y ) {
var date = new Date( y, m, d, 0, 0, 0, 0 );
// First week of the year always has 4th January in it
date.setDate( date.getDate() + 4 - (date.getDay() || 7) );
var oneJan = new Date( y, 0, 1 );
var weekNum = Math.ceil( ( ( (date - oneJan) / 86400000) + 1)/7 );
return '<td class="'+this.c.classPrefix+'-week">' + weekNum + '</td>';
},
/**
* Check if the instance has a date object value - it might be null.
* If is doesn't set one to now.
* @returns A Date object
* @private
*/
_needValue: function () {
if ( ! this.s.d ) {
this.s.d = this._dateToUtc( new Date() );
}
return this.s.d;
},
/**
* Create option elements from a range in an array
*
* @param {string} selector Class name unique to the select element to use
* @param {array} values Array of values
* @param {array} [labels] Array of labels. If given must be the same
* length as the values parameter.
* @private
*/
_options: function ( selector, values, labels ) {
if ( ! labels ) {
labels = values;
}
var select = this.dom.container.find('select.'+this.c.classPrefix+'-'+selector);
select.empty();
for ( var i=0, ien=values.length ; i<ien ; i++ ) {
select.append( '<option value="'+values[i]+'">'+labels[i]+'</option>' );
}
},
/**
* Set an option and update the option's span pair (since the select element
* has opacity 0 for styling)
*
* @param {string} selector Class name unique to the select element to use
* @param {*} val Value to set
* @private
*/
_optionSet: function ( selector, val ) {
var select = this.dom.container.find('select.'+this.c.classPrefix+'-'+selector);
var span = select.parent().children('span');
select.val( val );
var selected = select.find('option:selected');
span.html( selected.length !== 0 ?
selected.text() :
this.c.i18n.unknown
);
},
/**
* Create time options list.
*
* @param {string} unit Time unit - hours, minutes or seconds
* @param {integer} count Count range - 12, 24 or 60
* @param {integer} val Existing value for this unit
* @param {integer[]} allowed Values allow for selection
* @param {integer} range Override range
* @private
*/
_optionsTime: function ( unit, count, val, allowed, range ) {
var classPrefix = this.c.classPrefix;
var container = this.dom.container.find('div.'+classPrefix+'-'+unit);
var i, j;
var render = count === 12 ?
function (i) { return i; } :
this._pad;
var classPrefix = this.c.classPrefix;
var className = classPrefix+'-table';
var i18n = this.c.i18n;
if ( ! container.length ) {
return;
}
var a = '';
var span = 10;
var button = function (value, label, className) {
// Shift the value for PM
if ( count === 12 && typeof value === 'number' ) {
if (val >= 12 ) {
value += 12;
}
if (value == 12) {
value = 0;
}
else if (value == 24) {
value = 12;
}
}
var selected = val === value || (value === 'am' && val < 12) || (value === 'pm' && val >= 12) ?
'selected' :
'';
if (allowed && $.inArray(value, allowed) === -1) {
selected += ' disabled';
}
if ( className ) {
selected += ' '+className;
}
return '<td class="selectable '+selected+'">' +
'<button class="'+classPrefix+'-button '+classPrefix+'-day" type="button" data-unit="'+unit+'" data-value="'+value+ '">' +
'<span>'+label+'</span>'+
'</button>' +
'</td>';
}
if ( count === 12 ) {
// Hours with AM/PM
a += '<tr>';
for ( i=1 ; i<=6 ; i++ ) {
a += button(i, render(i));
}
a += button('am', i18n.amPm[0]);
a += '</tr>';
a += '<tr>';
for ( i=7 ; i<=12 ; i++ ) {
a += button(i, render(i));
}
a += button('pm', i18n.amPm[1]);
a += '</tr>';
span = 7;
}
else if ( count === 24 ) {
// Hours - 24
var c = 0;
for (j=0 ; j<4 ; j++ ) {
a += '<tr>';
for ( i=0 ; i<6 ; i++ ) {
a += button(c, render(c));
c++;
}
a += '</tr>';
}
span = 6;
}
else {
// Minutes and seconds
a += '<tr>';
for (j=0 ; j<60 ; j+=10 ) {
a += button(j, render(j), 'range');
}
a += '</tr>';
// Slight hack to allow for the different number of columns
a += '</tbody></thead><table class="'+className+' '+className+'-nospace"><tbody>';
var start = range !== null ?
range :
Math.floor( val / 10 )*10;
a += '<tr>';
for (j=start+1 ; j<start+10 ; j++ ) {
a += button(j, render(j));
}
a += '</tr>';
span = 6;
}
container
.empty()
.append(
'<table class="'+className+'">'+
'<thead><tr><th colspan="'+span+'">'+
i18n[unit] +
'</th></tr></thead>'+
'<tbody>'+
a+
'</tbody>'+
'</table>'
);
},
/**
* Create the options for the month and year
*
* @param {integer} year Year
* @param {integer} month Month (starting at 0)
* @private
*/
_optionsTitle: function () {
var i18n = this.c.i18n;
var min = this.c.minDate;
var max = this.c.maxDate;
var minYear = min ? min.getFullYear() : null;
var maxYear = max ? max.getFullYear() : null;
var i = minYear !== null ? minYear : new Date().getFullYear() - this.c.yearRange;
var j = maxYear !== null ? maxYear : new Date().getFullYear() + this.c.yearRange;
this._options( 'month', this._range( 0, 11 ), i18n.months );
this._options( 'year', this._range( i, j ) );
},
/**
* Simple two digit pad
*
* @param {integer} i Value that might need padding
* @return {string|integer} Padded value
* @private
*/
_pad: function ( i ) {
return i<10 ? '0'+i : i;
},
/**
* Position the calendar to look attached to the input element
* @private
*/
_position: function () {
var offset = this.c.attachTo === 'input' ? this.dom.input.position() : this.dom.input.offset();
var container = this.dom.container;
var inputHeight = this.dom.input.outerHeight();
if (container.hasClass('inline')) {
container.insertAfter( this.dom.input );
return;
}
if ( this.s.parts.date && this.s.parts.time && $(window).width() > 550 ) {
container.addClass('horizontal');
}
else {
container.removeClass('horizontal');
}
if(this.c.attachTo === 'input') {
container
.css( {
top: offset.top + inputHeight,
left: offset.left
} )
.insertAfter( this.dom.input );
}
else {
container
.css( {
top: offset.top + inputHeight,
left: offset.left
} )
.appendTo( 'body' );
}
var calHeight = container.outerHeight();
var calWidth = container.outerWidth();
var scrollTop = $(window).scrollTop();
// Correct to the bottom
if ( offset.top + inputHeight + calHeight - scrollTop > $(window).height() ) {
var newTop = offset.top - calHeight;
container.css( 'top', newTop < 0 ? 0 : newTop );
}
// Correct to the right
if ( calWidth + offset.left > $(window).width() ) {
var newLeft = $(window).width() - calWidth;
// Account for elements which are inside a position absolute element
if (this.c.attachTo === 'input') {
newLeft -= $(container).offsetParent().offset().left;
}
container.css( 'left', newLeft < 0 ? 0 : newLeft );
}
},
/**
* Create a simple array with a range of values
*
* @param {integer} start Start value (inclusive)
* @param {integer} end End value (inclusive)
* @param {integer} [inc=1] Increment value
* @return {array} Created array
* @private
*/
_range: function ( start, end, inc ) {
var a = [];
if ( ! inc ) {
inc = 1;
}
for ( var i=start ; i<=end ; i+=inc ) {
a.push( i );
}
return a;
},
/**
* Redraw the calendar based on the display date - this is a destructive
* operation
*
* @private
*/
_setCalander: function () {
if ( this.s.display ) {
this.dom.calendar
.empty()
.append( this._htmlMonth(
this.s.display.getUTCFullYear(),
this.s.display.getUTCMonth()
) );
}
},
/**
* Set the month and year for the calendar based on the current display date
*
* @private
*/
_setTitle: function () {
this._optionSet( 'month', this.s.display.getUTCMonth() );
this._optionSet( 'year', this.s.display.getUTCFullYear() );
},
/**
* Set the time based on the current value of the widget
*
* @private
*/
_setTime: function () {
var that = this;
var d = this.s.d;
// luxon uses different method names so need to be able to call them. This happens a few time later in this method too
var luxDT = null
if (dateLib && dateLib == window.luxon) {
luxDT = dateLib.DateTime.fromJSDate(d);
}
var hours = luxDT != null
? luxDT.hour
: d
? d.getUTCHours()
: 0;
var allowed = function ( prop ) { // Backwards compt with `Increment` option
return that.c[prop+'Available'] ?
that.c[prop+'Available'] :
that._range( 0, 59, that.c[prop+'Increment'] );
}
this._optionsTime( 'hours', this.s.parts.hours12 ? 12 : 24, hours, this.c.hoursAvailable )
this._optionsTime(
'minutes',
60,
luxDT != null
? luxDT.minute
: d
? d.getUTCMinutes()
: 0, allowed('minutes'),
this.s.minutesRange
);
this._optionsTime(
'seconds',
60,
luxDT != null
? luxDT.second
: d
? d.getSeconds()
: 0,
allowed('seconds'),
this.s.secondsRange
);
},
/**
* Show the widget and add events to the document required only while it
* is displayed
*
* @private
*/
_show: function () {
var that = this;
var namespace = this.s.namespace;
this._position();
// Need to reposition on scroll
$(window).on( 'scroll.'+namespace+' resize.'+namespace, function () {
that._position();
} );
$('div.DTE_Body_Content').on( 'scroll.'+namespace, function () {
that._position();
} );
$('div.dataTables_scrollBody').on( 'scroll.'+namespace, function () {
that._position();
} );
var offsetParent = this.dom.input[0].offsetParent;
if ( offsetParent !== document.body ) {
$(offsetParent).on( 'scroll.'+namespace, function () {
that._position();
} );
}
// On tab focus will move to a different field (no keyboard navigation
// in the date picker - this might need to be changed).
$(document).on( 'keydown.'+namespace, function (e) {
if (
e.keyCode === 9 || // tab
e.keyCode === 27 || // esc
e.keyCode === 13 // return
) {
that._hide();
}
} );
// Hide if clicking outside of the widget - but in a different click
// event from the one that was used to trigger the show (bubble and
// inline)
setTimeout( function () {
$('body').on( 'click.'+namespace, function (e) {
var parents = $(e.target).parents();
if ( ! parents.filter( that.dom.container ).length && e.target !== that.dom.input[0] ) {
that._hide();
}
} );
}, 10 );
},
/**
* Write the formatted string to the input element this control is attached
* to
*
* @private
*/
_writeOutput: function ( focus ) {
var date = this.s.d;
var out = '';
// Use moment, dayjs or luxon if possible - otherwise it must be ISO8601 (or the
// constructor would have thrown an error)
// luxon uses different method names so need to be able to call them.
if (date) {
out = dateLib && dateLib == window.luxon
? dateLib.DateTime.fromJSDate(this.s.d).toFormat(this.c.format)
: dateLib ?
dateLib.utc( date, undefined, this.c.locale, this.c.strict ).format( this.c.format ) :
date.getUTCFullYear() +'-'+
this._pad(date.getUTCMonth() + 1) +'-'+
this._pad(date.getUTCDate());
}
this.dom.input
.val( out )
.trigger('change', {write: date});
if ( this.dom.input.attr('type') === 'hidden' ) {
this.val(out, false);
}
if ( focus ) {
this.dom.input.focus();
}
}
} );
/**
* Use a specificmoment compatible date library
*/
DateTime.use = function (lib) {
dateLib = lib;
};
/**
* For generating unique namespaces
*
* @type {Number}
* @private
*/
DateTime._instance = 0;
/**
* Defaults for the date time picker
*
* @type {Object}
*/
DateTime.defaults = {
attachTo: 'body',
buttons: {
clear: false,
today: false
},
// Not documented - could be an internal property
classPrefix: 'dt-datetime',
// function or array of ints
disableDays: null,
// first day of the week (0: Sunday, 1: Monday, etc)
firstDay: 1,
format: 'YYYY-MM-DD',
hoursAvailable: null,
i18n: {
clear: 'Clear',
previous: 'Previous',
next: 'Next',
months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
weekdays: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
amPm: [ 'am', 'pm' ],
hours: 'Hour',
minutes: 'Minute',
seconds: 'Second',
unknown: '-',
today: 'Today'
},
maxDate: null,
minDate: null,
minutesAvailable: null,
minutesIncrement: 1, // deprecated
strict: true,
locale: 'en',
onChange: function () {},
secondsAvailable: null,
secondsIncrement: 1, // deprecated
// show the ISO week number at the head of the row
showWeekNumber: false,
// overruled by max / min date
yearRange: 25
};
DateTime.version = '1.1.0';
// Global export - if no conflicts
if (! window.DateTime) {
window.DateTime = DateTime;
}
// Make available via jQuery
$.fn.dtDateTime = function (options) {
return this.each(function() {
new DateTime(this, options);
});
}
// Attach to DataTables if present
if ($.fn.dataTable) {
$.fn.dataTable.DateTime = DateTime;
$.fn.DataTable.DateTime = DateTime;
if ($.fn.dataTable.Editor) {
$.fn.dataTable.Editor.DateTime = DateTime;
}
}
return DateTime;
}));