+ * @type array
+ * @default []
+ */
+ "aanFeatures": [],
+
+ /**
+ * Store data information - see {@link DataTable.models.oRow} for detailed
+ * information.
+ * @type array
+ * @default []
+ */
+ "aoData": [],
+
+ /**
+ * Array of indexes which are in the current display (after filtering etc)
+ * @type array
+ * @default []
+ */
+ "aiDisplay": [],
+
+ /**
+ * Array of indexes for display - no filtering
+ * @type array
+ * @default []
+ */
+ "aiDisplayMaster": [],
+
+ /**
+ * Map of row ids to data indexes
+ * @type object
+ * @default {}
+ */
+ "aIds": {},
+
+ /**
+ * Store information about each column that is in use
+ * @type array
+ * @default []
+ */
+ "aoColumns": [],
+
+ /**
+ * Store information about the table's header
+ * @type array
+ * @default []
+ */
+ "aoHeader": [],
+
+ /**
+ * Store information about the table's footer
+ * @type array
+ * @default []
+ */
+ "aoFooter": [],
+
+ /**
+ * Store the applied global search information in case we want to force a
+ * research or compare the old search to a new one.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @namespace
+ * @extends DataTable.models.oSearch
+ */
+ "oPreviousSearch": {},
+
+ /**
+ * Store the applied search for each column - see
+ * {@link DataTable.models.oSearch} for the format that is used for the
+ * filtering information for each column.
+ * @type array
+ * @default []
+ */
+ "aoPreSearchCols": [],
+
+ /**
+ * Sorting that is applied to the table. Note that the inner arrays are
+ * used in the following manner:
+ *
+ *
Index 0 - column number
+ *
Index 1 - current sorting direction
+ *
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @todo These inner arrays should really be objects
+ */
+ "aaSorting": null,
+
+ /**
+ * Sorting that is always applied to the table (i.e. prefixed in front of
+ * aaSorting).
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "aaSortingFixed": [],
+
+ /**
+ * Classes to use for the striping of a table.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type array
+ * @default []
+ */
+ "asStripeClasses": null,
+
+ /**
+ * If restoring a table - we should restore its striping classes as well
+ * @type array
+ * @default []
+ */
+ "asDestroyStripes": [],
+
+ /**
+ * If restoring a table - we should restore its width
+ * @type int
+ * @default 0
+ */
+ "sDestroyWidth": 0,
+
+ /**
+ * Callback functions array for every time a row is inserted (i.e. on a draw).
+ * @type array
+ * @default []
+ */
+ "aoRowCallback": [],
+
+ /**
+ * Callback functions for the header on each draw.
+ * @type array
+ * @default []
+ */
+ "aoHeaderCallback": [],
+
+ /**
+ * Callback function for the footer on each draw.
+ * @type array
+ * @default []
+ */
+ "aoFooterCallback": [],
+
+ /**
+ * Array of callback functions for draw callback functions
+ * @type array
+ * @default []
+ */
+ "aoDrawCallback": [],
+
+ /**
+ * Array of callback functions for row created function
+ * @type array
+ * @default []
+ */
+ "aoRowCreatedCallback": [],
+
+ /**
+ * Callback functions for just before the table is redrawn. A return of
+ * false will be used to cancel the draw.
+ * @type array
+ * @default []
+ */
+ "aoPreDrawCallback": [],
+
+ /**
+ * Callback functions for when the table has been initialised.
+ * @type array
+ * @default []
+ */
+ "aoInitComplete": [],
+
+
+ /**
+ * Callbacks for modifying the settings to be stored for state saving, prior to
+ * saving state.
+ * @type array
+ * @default []
+ */
+ "aoStateSaveParams": [],
+
+ /**
+ * Callbacks for modifying the settings that have been stored for state saving
+ * prior to using the stored values to restore the state.
+ * @type array
+ * @default []
+ */
+ "aoStateLoadParams": [],
+
+ /**
+ * Callbacks for operating on the settings object once the saved state has been
+ * loaded
+ * @type array
+ * @default []
+ */
+ "aoStateLoaded": [],
+
+ /**
+ * Cache the table ID for quick access
+ * @type string
+ * @default Empty string
+ */
+ "sTableId": "",
+
+ /**
+ * The TABLE node for the main table
+ * @type node
+ * @default null
+ */
+ "nTable": null,
+
+ /**
+ * Permanent ref to the thead element
+ * @type node
+ * @default null
+ */
+ "nTHead": null,
+
+ /**
+ * Permanent ref to the tfoot element - if it exists
+ * @type node
+ * @default null
+ */
+ "nTFoot": null,
+
+ /**
+ * Permanent ref to the tbody element
+ * @type node
+ * @default null
+ */
+ "nTBody": null,
+
+ /**
+ * Cache the wrapper node (contains all DataTables controlled elements)
+ * @type node
+ * @default null
+ */
+ "nTableWrapper": null,
+
+ /**
+ * Indicate if when using server-side processing the loading of data
+ * should be deferred until the second draw.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type boolean
+ * @default false
+ */
+ "bDeferLoading": false,
+
+ /**
+ * Indicate if all required information has been read in
+ * @type boolean
+ * @default false
+ */
+ "bInitialised": false,
+
+ /**
+ * Information about open rows. Each object in the array has the parameters
+ * 'nTr' and 'nParent'
+ * @type array
+ * @default []
+ */
+ "aoOpenRows": [],
+
+ /**
+ * Dictate the positioning of DataTables' control elements - see
+ * {@link DataTable.model.oInit.sDom}.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default null
+ */
+ "sDom": null,
+
+ /**
+ * Search delay (in mS)
+ * @type integer
+ * @default null
+ */
+ "searchDelay": null,
+
+ /**
+ * Which type of pagination should be used.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type string
+ * @default two_button
+ */
+ "sPaginationType": "two_button",
+
+ /**
+ * The state duration (for `stateSave`) in seconds.
+ * Note that this parameter will be set by the initialisation routine. To
+ * set a default use {@link DataTable.defaults}.
+ * @type int
+ * @default 0
+ */
+ "iStateDuration": 0,
+
+ /**
+ * Array of callback functions for state saving. Each array element is an
+ * object with the following parameters:
+ *
+ *
function:fn - function to call. Takes two parameters, oSettings
+ * and the JSON string to save that has been thus far created. Returns
+ * a JSON string to be inserted into a json object
+ * (i.e. '"param": [ 0, 1, 2]')
+ *
string:sName - name of callback
+ *
+ * @type array
+ * @default []
+ */
+ "aoStateSave": [],
+
+ /**
+ * Array of callback functions for state loading. Each array element is an
+ * object with the following parameters:
+ *
+ *
function:fn - function to call. Takes two parameters, oSettings
+ * and the object stored. May return false to cancel state loading
'
+ );
+
+ 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 '
';
+ },
+
+ /**
+ * 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( '
' );
+ }
+
+ for ( var i=0 ; i<7 ; i++ ) {
+ a.push( '
'+dayName( i )+'
' );
+ }
+
+ 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 '
' + weekNum + '
';
+ },
+
+ /**
+ * 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'+labels[i]+'' );
+ }
+ },
+
+ /**
+ * 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 '
' +
+ '' +
+ '
';
+ }
+
+ if ( count === 12 ) {
+ // Hours with AM/PM
+ a += '
';
+
+ for ( i=1 ; i<=6 ; i++ ) {
+ a += button(i, render(i));
+ }
+ a += button('am', i18n.amPm[0]);
+
+ a += '
';
+ a += '
';
+
+ for ( i=7 ; i<=12 ; i++ ) {
+ a += button(i, render(i));
+ }
+ a += button('pm', i18n.amPm[1]);
+ a += '
';
+
+ span = 7;
+ }
+ else if ( count === 24 ) {
+ // Hours - 24
+ var c = 0;
+ for (j=0 ; j<4 ; j++ ) {
+ a += '
';
+ for ( i=0 ; i<6 ; i++ ) {
+ a += button(c, render(c));
+ c++;
+ }
+ a += '