| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564 |
- /*! DateTime picker for DataTables.net v1.1.2
- *
- * © SpryMedia Ltd, all rights reserved.
- * License: MIT datatables.net/license/mit
- */
- /**
- * @summary DateTime picker for DataTables.net
- * @version 1.1.2
- * @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 type="button" title="'+i18n.previous+'">'+i18n.previous+'</button>'+
- '</div>'+
- '<div class="'+classPrefix+'-iconRight">'+
- '<button type="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);
- that._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 );
- $(this.dom.input[0].offsetParent).off('.'+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.2';
- // 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;
- }));
|