1564 lines
38 KiB
JavaScript
1564 lines
38 KiB
JavaScript
|
/*! 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;
|
||
|
|
||
|
}));
|