oxidized-web 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of oxidized-web might be problematic. Click here for more details.

@@ -1,25 +1,7 @@
1
- /*! DataTables 2.0.8
1
+ /*! DataTables 2.2.2
2
2
  * © SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
5
- /**
6
- * @summary DataTables
7
- * @description Paginate, search and order HTML tables
8
- * @version 2.0.8
9
- * @author SpryMedia Ltd
10
- * @contact www.datatables.net
11
- * @copyright SpryMedia Ltd.
12
- *
13
- * This source file is free software, available under the following license:
14
- * MIT license - https://datatables.net/license
15
- *
16
- * This source file is distributed in the hope that it will be useful, but
17
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
18
- * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
19
- *
20
- * For details please refer to: https://www.datatables.net
21
- */
22
-
23
5
  (function( factory ) {
24
6
  "use strict";
25
7
 
@@ -104,7 +86,6 @@
104
86
 
105
87
  var i=0, iLen;
106
88
  var sId = this.getAttribute( 'id' );
107
- var bInitHandedOff = false;
108
89
  var defaults = DataTable.defaults;
109
90
  var $this = $(this);
110
91
 
@@ -254,6 +235,8 @@
254
235
  "rowId",
255
236
  "caption",
256
237
  "layout",
238
+ "orderDescReverse",
239
+ "typeDetect",
257
240
  [ "iCookieDuration", "iStateDuration" ], // backwards compat
258
241
  [ "oSearch", "oPreviousSearch" ],
259
242
  [ "aoSearchCols", "aoPreSearchCols" ],
@@ -300,38 +283,14 @@
300
283
  oSettings._iDisplayStart = oInit.iDisplayStart;
301
284
  }
302
285
 
303
- /* Language definitions */
304
- var oLanguage = oSettings.oLanguage;
305
- $.extend( true, oLanguage, oInit.oLanguage );
306
-
307
- if ( oLanguage.sUrl )
286
+ var defer = oInit.iDeferLoading;
287
+ if ( defer !== null )
308
288
  {
309
- /* Get the language definitions from a file - because this Ajax call makes the language
310
- * get async to the remainder of this function we use bInitHandedOff to indicate that
311
- * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor
312
- */
313
- $.ajax( {
314
- dataType: 'json',
315
- url: oLanguage.sUrl,
316
- success: function ( json ) {
317
- _fnCamelToHungarian( defaults.oLanguage, json );
318
- $.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
289
+ oSettings.deferLoading = true;
319
290
 
320
- _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
321
- _fnInitialise( oSettings );
322
- },
323
- error: function () {
324
- // Error occurred loading language file
325
- _fnLog( oSettings, 0, 'i18n file loading error', 21 );
326
-
327
- // continue on as best we can
328
- _fnInitialise( oSettings );
329
- }
330
- } );
331
- bInitHandedOff = true;
332
- }
333
- else {
334
- _fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
291
+ var tmp = Array.isArray(defer);
292
+ oSettings._iRecordsDisplay = tmp ? defer[0] : defer;
293
+ oSettings._iRecordsTotal = tmp ? defer[1] : defer;
335
294
  }
336
295
 
337
296
  /*
@@ -398,113 +357,110 @@
398
357
  } );
399
358
  }
400
359
 
360
+ // Must be done after everything which can be overridden by the state saving!
361
+ _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
362
+
401
363
  var features = oSettings.oFeatures;
402
- var loadedInit = function () {
403
- /*
404
- * Sorting
405
- * @todo For modularisation (1.11) this needs to do into a sort start up handler
406
- */
364
+ if ( oInit.bStateSave )
365
+ {
366
+ features.bStateSave = true;
367
+ }
407
368
 
408
- // If aaSorting is not defined, then we use the first indicator in asSorting
409
- // in case that has been altered, so the default sort reflects that option
410
- if ( oInit.aaSorting === undefined ) {
411
- var sorting = oSettings.aaSorting;
412
- for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
413
- sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
414
- }
369
+ // If aaSorting is not defined, then we use the first indicator in asSorting
370
+ // in case that has been altered, so the default sort reflects that option
371
+ if ( oInit.aaSorting === undefined ) {
372
+ var sorting = oSettings.aaSorting;
373
+ for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
374
+ sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
415
375
  }
376
+ }
416
377
 
417
- /* Do a first pass on the sorting classes (allows any size changes to be taken into
418
- * account, and also will apply sorting disabled classes if disabled
419
- */
420
- _fnSortingClasses( oSettings );
421
-
422
- _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
423
- if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
424
- _fnSortingClasses( oSettings );
425
- }
426
- } );
378
+ // Do a first pass on the sorting classes (allows any size changes to be taken into
379
+ // account, and also will apply sorting disabled classes if disabled
380
+ _fnSortingClasses( oSettings );
427
381
 
382
+ _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
383
+ if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
384
+ _fnSortingClasses( oSettings );
385
+ }
386
+ } );
428
387
 
429
- /*
430
- * Final init
431
- * Cache the header, body and footer as required, creating them if needed
432
- */
433
- var caption = $this.children('caption');
434
388
 
435
- if ( oSettings.caption ) {
436
- if ( caption.length === 0 ) {
437
- caption = $('<caption/>').appendTo( $this );
438
- }
389
+ /*
390
+ * Table HTML init
391
+ * Cache the header, body and footer as required, creating them if needed
392
+ */
393
+ var caption = $this.children('caption');
439
394
 
440
- caption.html( oSettings.caption );
395
+ if ( oSettings.caption ) {
396
+ if ( caption.length === 0 ) {
397
+ caption = $('<caption/>').appendTo( $this );
441
398
  }
442
399
 
443
- // Store the caption side, so we can remove the element from the document
444
- // when creating the element
445
- if (caption.length) {
446
- caption[0]._captionSide = caption.css('caption-side');
447
- oSettings.captionNode = caption[0];
448
- }
400
+ caption.html( oSettings.caption );
401
+ }
449
402
 
450
- if ( thead.length === 0 ) {
451
- thead = $('<thead/>').appendTo($this);
452
- }
453
- oSettings.nTHead = thead[0];
454
- $('tr', thead).addClass(oClasses.thead.row);
403
+ // Store the caption side, so we can remove the element from the document
404
+ // when creating the element
405
+ if (caption.length) {
406
+ caption[0]._captionSide = caption.css('caption-side');
407
+ oSettings.captionNode = caption[0];
408
+ }
455
409
 
456
- var tbody = $this.children('tbody');
457
- if ( tbody.length === 0 ) {
458
- tbody = $('<tbody/>').insertAfter(thead);
459
- }
460
- oSettings.nTBody = tbody[0];
410
+ if ( thead.length === 0 ) {
411
+ thead = $('<thead/>').appendTo($this);
412
+ }
413
+ oSettings.nTHead = thead[0];
461
414
 
462
- var tfoot = $this.children('tfoot');
463
- if ( tfoot.length === 0 ) {
464
- // If we are a scrolling table, and no footer has been given, then we need to create
465
- // a tfoot element for the caption element to be appended to
466
- tfoot = $('<tfoot/>').appendTo($this);
467
- }
468
- oSettings.nTFoot = tfoot[0];
469
- $('tr', tfoot).addClass(oClasses.tfoot.row);
415
+ var tbody = $this.children('tbody');
416
+ if ( tbody.length === 0 ) {
417
+ tbody = $('<tbody/>').insertAfter(thead);
418
+ }
419
+ oSettings.nTBody = tbody[0];
470
420
 
471
- // Check if there is data passing into the constructor
472
- if ( oInit.aaData ) {
473
- for ( i=0 ; i<oInit.aaData.length ; i++ ) {
474
- _fnAddData( oSettings, oInit.aaData[ i ] );
475
- }
476
- }
477
- else if ( _fnDataSource( oSettings ) == 'dom' ) {
478
- // Grab the data from the page
479
- _fnAddTr( oSettings, $(oSettings.nTBody).children('tr') );
480
- }
421
+ var tfoot = $this.children('tfoot');
422
+ if ( tfoot.length === 0 ) {
423
+ // If we are a scrolling table, and no footer has been given, then we need to create
424
+ // a tfoot element for the caption element to be appended to
425
+ tfoot = $('<tfoot/>').appendTo($this);
426
+ }
427
+ oSettings.nTFoot = tfoot[0];
481
428
 
482
- /* Copy the data index array */
483
- oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
429
+ // Copy the data index array
430
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
484
431
 
485
- /* Initialisation complete - table can be drawn */
486
- oSettings.bInitialised = true;
432
+ // Initialisation complete - table can be drawn
433
+ oSettings.bInitialised = true;
487
434
 
488
- /* Check if we need to initialise the table (it might not have been handed off to the
489
- * language processor)
490
- */
491
- if ( bInitHandedOff === false ) {
492
- _fnInitialise( oSettings );
493
- }
494
- };
435
+ // Language definitions
436
+ var oLanguage = oSettings.oLanguage;
437
+ $.extend( true, oLanguage, oInit.oLanguage );
495
438
 
496
- /* Must be done after everything which can be overridden by the state saving! */
497
- _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
439
+ if ( oLanguage.sUrl ) {
440
+ // Get the language definitions from a file
441
+ $.ajax( {
442
+ dataType: 'json',
443
+ url: oLanguage.sUrl,
444
+ success: function ( json ) {
445
+ _fnCamelToHungarian( defaults.oLanguage, json );
446
+ $.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
498
447
 
499
- if ( oInit.bStateSave )
500
- {
501
- features.bStateSave = true;
502
- _fnLoadState( oSettings, oInit, loadedInit );
448
+ _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
449
+ _fnInitialise( oSettings );
450
+ },
451
+ error: function () {
452
+ // Error occurred loading language file
453
+ _fnLog( oSettings, 0, 'i18n file loading error', 21 );
454
+
455
+ // Continue on as best we can
456
+ _fnInitialise( oSettings );
457
+ }
458
+ } );
503
459
  }
504
460
  else {
505
- loadedInit();
461
+ _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
462
+ _fnInitialise( oSettings );
506
463
  }
507
-
508
464
  } );
509
465
  _that = null;
510
466
  return this;
@@ -1021,6 +977,15 @@
1021
977
  info: {
1022
978
  container: 'dt-info'
1023
979
  },
980
+ layout: {
981
+ row: 'dt-layout-row',
982
+ cell: 'dt-layout-cell',
983
+ tableRow: 'dt-layout-table',
984
+ tableCell: '',
985
+ start: 'dt-layout-start',
986
+ end: 'dt-layout-end',
987
+ full: 'dt-layout-full'
988
+ },
1024
989
  length: {
1025
990
  container: 'dt-length',
1026
991
  select: 'dt-input'
@@ -1069,7 +1034,8 @@
1069
1034
  active: 'current',
1070
1035
  button: 'dt-paging-button',
1071
1036
  container: 'dt-paging',
1072
- disabled: 'disabled'
1037
+ disabled: 'disabled',
1038
+ nav: ''
1073
1039
  }
1074
1040
  } );
1075
1041
 
@@ -1144,7 +1110,7 @@
1144
1110
  };
1145
1111
 
1146
1112
 
1147
- var _isNumber = function ( d, decimalPoint, formatted ) {
1113
+ var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) {
1148
1114
  var type = typeof d;
1149
1115
  var strType = type === 'string';
1150
1116
 
@@ -1155,7 +1121,7 @@
1155
1121
  // If empty return immediately so there must be a number if it is a
1156
1122
  // formatted string (this stops the string "k", or "kr", etc being detected
1157
1123
  // as a formatted number for currency
1158
- if ( _empty( d ) ) {
1124
+ if ( allowEmpty && _empty( d ) ) {
1159
1125
  return true;
1160
1126
  }
1161
1127
 
@@ -1177,8 +1143,8 @@
1177
1143
  };
1178
1144
 
1179
1145
  // Is a string a number surrounded by HTML?
1180
- var _htmlNumeric = function ( d, decimalPoint, formatted ) {
1181
- if ( _empty( d ) ) {
1146
+ var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) {
1147
+ if ( allowEmpty && _empty( d ) ) {
1182
1148
  return true;
1183
1149
  }
1184
1150
 
@@ -1190,7 +1156,7 @@
1190
1156
  var html = _isHtml( d );
1191
1157
  return ! html ?
1192
1158
  null :
1193
- _isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
1159
+ _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ?
1194
1160
  true :
1195
1161
  null;
1196
1162
  };
@@ -1232,7 +1198,7 @@
1232
1198
  // is essential here
1233
1199
  if ( prop2 !== undefined ) {
1234
1200
  for ( ; i<ien ; i++ ) {
1235
- if ( a[ order[i] ][ prop ] ) {
1201
+ if ( a[ order[i] ] && a[ order[i] ][ prop ] ) {
1236
1202
  out.push( a[ order[i] ][ prop ][ prop2 ] );
1237
1203
  }
1238
1204
  }
@@ -1286,6 +1252,10 @@
1286
1252
 
1287
1253
  // Replaceable function in api.util
1288
1254
  var _stripHtml = function (input) {
1255
+ if (! input || typeof input !== 'string') {
1256
+ return input;
1257
+ }
1258
+
1289
1259
  // Irrelevant check to workaround CodeQL's false positive on the regex
1290
1260
  if (input.length > _max_str_len) {
1291
1261
  throw new Error('Exceeded max str len');
@@ -1328,8 +1298,11 @@
1328
1298
  }
1329
1299
 
1330
1300
  // It is faster to just run `normalize` than it is to check if
1331
- // we need to with a regex!
1332
- var res = str.normalize("NFD");
1301
+ // we need to with a regex! (Check as it isn't available in old
1302
+ // Safari)
1303
+ var res = str.normalize
1304
+ ? str.normalize("NFD")
1305
+ : str;
1333
1306
 
1334
1307
  // Equally, here we check if a regex is needed or not
1335
1308
  return res.length !== str.length
@@ -2168,6 +2141,10 @@
2168
2141
  var width = _fnColumnsSumWidth(settings, [i], false, false);
2169
2142
 
2170
2143
  cols[i].colEl.css('width', width);
2144
+
2145
+ if (settings.oScroll.sX) {
2146
+ cols[i].colEl.css('min-width', width);
2147
+ }
2171
2148
  }
2172
2149
  }
2173
2150
 
@@ -2252,6 +2229,21 @@
2252
2229
  return a;
2253
2230
  }
2254
2231
 
2232
+ /**
2233
+ * Allow the result from a type detection function to be `true` while
2234
+ * translating that into a string. Old type detection functions will
2235
+ * return the type name if it passes. An obect store would be better,
2236
+ * but not backwards compatible.
2237
+ *
2238
+ * @param {*} typeDetect Object or function for type detection
2239
+ * @param {*} res Result from the type detection function
2240
+ * @returns Type name or false
2241
+ */
2242
+ function _typeResult (typeDetect, res) {
2243
+ return res === true
2244
+ ? typeDetect._name
2245
+ : res;
2246
+ }
2255
2247
 
2256
2248
  /**
2257
2249
  * Calculate the 'type' of a column
@@ -2266,7 +2258,7 @@
2266
2258
  var i, ien, j, jen, k, ken;
2267
2259
  var col, detectedType, cache;
2268
2260
 
2269
- // For each column, spin over the
2261
+ // For each column, spin over the data type detection functions, seeing if one matches
2270
2262
  for ( i=0, ien=columns.length ; i<ien ; i++ ) {
2271
2263
  col = columns[i];
2272
2264
  cache = [];
@@ -2275,9 +2267,34 @@
2275
2267
  col.sType = col._sManualType;
2276
2268
  }
2277
2269
  else if ( ! col.sType ) {
2270
+ // With SSP type detection can be unreliable and error prone, so we provide a way
2271
+ // to turn it off.
2272
+ if (! settings.typeDetect) {
2273
+ return;
2274
+ }
2275
+
2278
2276
  for ( j=0, jen=types.length ; j<jen ; j++ ) {
2279
- for ( k=0, ken=data.length ; k<ken ; k++ ) {
2277
+ var typeDetect = types[j];
2280
2278
 
2279
+ // There can be either one, or three type detection functions
2280
+ var oneOf = typeDetect.oneOf;
2281
+ var allOf = typeDetect.allOf || typeDetect;
2282
+ var init = typeDetect.init;
2283
+ var one = false;
2284
+
2285
+ detectedType = null;
2286
+
2287
+ // Fast detect based on column assignment
2288
+ if (init) {
2289
+ detectedType = _typeResult(typeDetect, init(settings, col, i));
2290
+
2291
+ if (detectedType) {
2292
+ col.sType = detectedType;
2293
+ break;
2294
+ }
2295
+ }
2296
+
2297
+ for ( k=0, ken=data.length ; k<ken ; k++ ) {
2281
2298
  if (! data[k]) {
2282
2299
  continue;
2283
2300
  }
@@ -2288,14 +2305,20 @@
2288
2305
  cache[k] = _fnGetCellData( settings, k, i, 'type' );
2289
2306
  }
2290
2307
 
2291
- detectedType = types[j]( cache[k], settings );
2308
+ // Only one data point in the column needs to match this function
2309
+ if (oneOf && ! one) {
2310
+ one = _typeResult(typeDetect, oneOf( cache[k], settings ));
2311
+ }
2312
+
2313
+ // All data points need to match this function
2314
+ detectedType = _typeResult(typeDetect, allOf( cache[k], settings ));
2292
2315
 
2293
2316
  // If null, then this type can't apply to this column, so
2294
2317
  // rather than testing all cells, break out. There is an
2295
2318
  // exception for the last type which is `html`. We need to
2296
2319
  // scan all rows since it is possible to mix string and HTML
2297
2320
  // types
2298
- if ( ! detectedType && j !== types.length-2 ) {
2321
+ if ( ! detectedType && j !== types.length-3 ) {
2299
2322
  break;
2300
2323
  }
2301
2324
 
@@ -2309,7 +2332,7 @@
2309
2332
 
2310
2333
  // Type is valid for all data points in the column - use this
2311
2334
  // type
2312
- if ( detectedType ) {
2335
+ if ( (oneOf && one && detectedType) || (!oneOf && detectedType) ) {
2313
2336
  col.sType = detectedType;
2314
2337
  break;
2315
2338
  }
@@ -2331,7 +2354,7 @@
2331
2354
 
2332
2355
  var renderer = _ext.type.render[col.sType];
2333
2356
 
2334
- // This can only happen once! There is no way to remover
2357
+ // This can only happen once! There is no way to remove
2335
2358
  // a renderer. After the first time the renderer has
2336
2359
  // already been set so createTr will run the renderer itself.
2337
2360
  if (renderer && ! col._render) {
@@ -3001,8 +3024,8 @@
3001
3024
  * @returns
3002
3025
  */
3003
3026
  function _fnGetRowDisplay (settings, rowIdx) {
3004
- let rowModal = settings.aoData[rowIdx];
3005
- let columns = settings.aoColumns;
3027
+ var rowModal = settings.aoData[rowIdx];
3028
+ var columns = settings.aoColumns;
3006
3029
 
3007
3030
  if (! rowModal.displayData) {
3008
3031
  // Need to render and cache
@@ -3087,6 +3110,9 @@
3087
3110
  _fnWriteCell(nTd, display[i]);
3088
3111
  }
3089
3112
 
3113
+ // column class
3114
+ _addClass(nTd, oCol.sClass);
3115
+
3090
3116
  // Visibility - add or remove as required
3091
3117
  if ( oCol.bVisible && create )
3092
3118
  {
@@ -3186,9 +3212,13 @@
3186
3212
 
3187
3213
  // Add the number of cells needed to make up to the number of columns
3188
3214
  if (row.length === 1) {
3189
- var cells = $('td, th', row);
3215
+ var cellCount = 0;
3216
+
3217
+ $('td, th', row).each(function () {
3218
+ cellCount += this.colSpan;
3219
+ });
3190
3220
 
3191
- for ( i=cells.length, ien=columns.length ; i<ien ; i++ ) {
3221
+ for ( i=cellCount, ien=columns.length ; i<ien ; i++ ) {
3192
3222
  $('<th/>')
3193
3223
  .html( columns[i][titleProp] || '' )
3194
3224
  .appendTo( row );
@@ -3200,14 +3230,13 @@
3200
3230
 
3201
3231
  if (side === 'header') {
3202
3232
  settings.aoHeader = detected;
3233
+ $('tr', target).addClass(classes.thead.row);
3203
3234
  }
3204
3235
  else {
3205
3236
  settings.aoFooter = detected;
3237
+ $('tr', target).addClass(classes.tfoot.row);
3206
3238
  }
3207
3239
 
3208
- // ARIA role for the rows
3209
- $(target).children('tr').attr('role', 'row');
3210
-
3211
3240
  // Every cell needs to be passed through the renderer
3212
3241
  $(target).children('tr').children('th, td')
3213
3242
  .each( function () {
@@ -3375,7 +3404,13 @@
3375
3404
  oSettings.bDrawing = true;
3376
3405
 
3377
3406
  /* Server-side processing draw intercept */
3378
- if ( !bServerSide )
3407
+ if ( oSettings.deferLoading )
3408
+ {
3409
+ oSettings.deferLoading = false;
3410
+ oSettings.iDraw++;
3411
+ _fnProcessingDisplay( oSettings, false );
3412
+ }
3413
+ else if ( !bServerSide )
3379
3414
  {
3380
3415
  oSettings.iDraw++;
3381
3416
  }
@@ -3412,7 +3447,6 @@
3412
3447
  var td = aoData.anCells[i];
3413
3448
 
3414
3449
  _addClass(td, _ext.type.className[col.sType]); // auto class
3415
- _addClass(td, col.sClass); // column class
3416
3450
  _addClass(td, oSettings.oClasses.tbody.cell); // all cells
3417
3451
  }
3418
3452
 
@@ -3476,6 +3510,9 @@
3476
3510
  filter = features.bFilter;
3477
3511
 
3478
3512
  if (recompute === undefined || recompute === true) {
3513
+ // Resolve any column types that are unknown due to addition or invalidation
3514
+ _fnColumnTypes( settings );
3515
+
3479
3516
  if ( sort ) {
3480
3517
  _fnSort( settings );
3481
3518
  }
@@ -3531,113 +3568,154 @@
3531
3568
 
3532
3569
 
3533
3570
  /**
3534
- * Convert a `layout` object given by a user to the object structure needed
3535
- * for the renderer. This is done twice, once for above and once for below
3536
- * the table. Ordering must also be considered.
3537
- *
3538
- * @param {*} settings DataTables settings object
3539
- * @param {*} layout Layout object to convert
3540
- * @param {string} side `top` or `bottom`
3541
- * @returns Converted array structure - one item for each row.
3571
+ * Expand the layout items into an object for the rendering function
3542
3572
  */
3543
- function _layoutArray ( settings, layout, side )
3544
- {
3545
- var groups = {};
3546
-
3547
- // Combine into like groups (e.g. `top`, `top2`, etc)
3548
- $.each( layout, function ( pos, val ) {
3549
- if (val === null) {
3550
- return;
3573
+ function _layoutItems (row, align, items) {
3574
+ if ( Array.isArray(items)) {
3575
+ for (var i=0 ; i<items.length ; i++) {
3576
+ _layoutItems(row, align, items[i]);
3551
3577
  }
3552
3578
 
3553
- var splitPos = pos.replace(/([A-Z])/g, ' $1').split(' ');
3579
+ return;
3580
+ }
3554
3581
 
3555
- if ( ! groups[ splitPos[0] ] ) {
3556
- groups[ splitPos[0] ] = {};
3557
- }
3582
+ var rowCell = row[align];
3558
3583
 
3559
- var align = splitPos.length === 1 ?
3560
- 'full' :
3561
- splitPos[1].toLowerCase();
3562
- var group = groups[ splitPos[0] ];
3563
- var groupRun = function (contents, innerVal) {
3564
- // If it is an object, then there can be multiple features contained in it
3565
- if ( $.isPlainObject( innerVal ) ) {
3566
- Object.keys(innerVal).map(function (key) {
3567
- contents.push( {
3568
- feature: key,
3569
- opts: innerVal[key]
3570
- });
3571
- });
3584
+ // If it is an object, then there can be multiple features contained in it
3585
+ if ( $.isPlainObject( items ) ) {
3586
+ // A feature plugin cannot be named "features" due to this check
3587
+ if (items.features) {
3588
+ if (items.rowId) {
3589
+ row.id = items.rowId;
3572
3590
  }
3573
- else {
3574
- contents.push(innerVal);
3591
+ if (items.rowClass) {
3592
+ row.className = items.rowClass;
3575
3593
  }
3576
- }
3577
3594
 
3578
- // Transform to an object with a contents property
3579
- if (! group[align] || ! group[align].contents) {
3580
- group[align] = { contents: [] };
3595
+ rowCell.id = items.id;
3596
+ rowCell.className = items.className;
3597
+
3598
+ _layoutItems(row, align, items.features);
3581
3599
  }
3600
+ else {
3601
+ Object.keys(items).map(function (key) {
3602
+ rowCell.contents.push( {
3603
+ feature: key,
3604
+ opts: items[key]
3605
+ });
3606
+ });
3607
+ }
3608
+ }
3609
+ else {
3610
+ rowCell.contents.push(items);
3611
+ }
3612
+ }
3613
+
3614
+ /**
3615
+ * Find, or create a layout row
3616
+ */
3617
+ function _layoutGetRow(rows, rowNum, align) {
3618
+ var row;
3619
+
3620
+ // Find existing rows
3621
+ for (var i=0; i<rows.length; i++) {
3622
+ row = rows[i];
3582
3623
 
3583
- // Allow for an array or just a single object
3584
- if ( Array.isArray(val)) {
3585
- for (var i=0 ; i<val.length ; i++) {
3586
- groupRun(group[align].contents, val[i]);
3624
+ if (row.rowNum === rowNum) {
3625
+ // full is on its own, but start and end share a row
3626
+ if (
3627
+ (align === 'full' && row.full) ||
3628
+ ((align === 'start' || align === 'end') && (row.start || row.end))
3629
+ ) {
3630
+ if (! row[align]) {
3631
+ row[align] = {
3632
+ contents: []
3633
+ };
3634
+ }
3635
+
3636
+ return row;
3587
3637
  }
3588
3638
  }
3589
- else {
3590
- groupRun(group[ align ].contents, val);
3639
+ }
3640
+
3641
+ // If we get this far, then there was no match, create a new row
3642
+ row = {
3643
+ rowNum: rowNum
3644
+ };
3645
+
3646
+ row[align] = {
3647
+ contents: []
3648
+ };
3649
+
3650
+ rows.push(row);
3651
+
3652
+ return row;
3653
+ }
3654
+
3655
+ /**
3656
+ * Convert a `layout` object given by a user to the object structure needed
3657
+ * for the renderer. This is done twice, once for above and once for below
3658
+ * the table. Ordering must also be considered.
3659
+ *
3660
+ * @param {*} settings DataTables settings object
3661
+ * @param {*} layout Layout object to convert
3662
+ * @param {string} side `top` or `bottom`
3663
+ * @returns Converted array structure - one item for each row.
3664
+ */
3665
+ function _layoutArray ( settings, layout, side ) {
3666
+ var rows = [];
3667
+
3668
+ // Split out into an array
3669
+ $.each( layout, function ( pos, items ) {
3670
+ if (items === null) {
3671
+ return;
3591
3672
  }
3592
3673
 
3593
- // And make contents an array
3594
- if ( ! Array.isArray( group[ align ].contents ) ) {
3595
- group[ align ].contents = [ group[ align ].contents ];
3674
+ var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/);
3675
+ var rowNum = parts[2]
3676
+ ? parts[2] * 1
3677
+ : 0;
3678
+ var align = parts[3]
3679
+ ? parts[3].toLowerCase()
3680
+ : 'full';
3681
+
3682
+ // Filter out the side we aren't interested in
3683
+ if (parts[1] !== side) {
3684
+ return;
3596
3685
  }
3597
- } );
3598
3686
 
3599
- var filtered = Object.keys(groups)
3600
- .map( function ( pos ) {
3601
- // Filter to only the side we need
3602
- if ( pos.indexOf(side) !== 0 ) {
3603
- return null;
3604
- }
3687
+ // Get or create the row we should attach to
3688
+ var row = _layoutGetRow(rows, rowNum, align);
3605
3689
 
3606
- return {
3607
- name: pos,
3608
- val: groups[pos]
3609
- };
3610
- } )
3611
- .filter( function (item) {
3612
- return item !== null;
3613
- });
3690
+ _layoutItems(row, align, items);
3691
+ });
3614
3692
 
3615
3693
  // Order by item identifier
3616
- filtered.sort( function ( a, b ) {
3617
- var order1 = a.name.replace(/[^0-9]/g, '') * 1;
3618
- var order2 = b.name.replace(/[^0-9]/g, '') * 1;
3694
+ rows.sort( function ( a, b ) {
3695
+ var order1 = a.rowNum;
3696
+ var order2 = b.rowNum;
3697
+
3698
+ // If both in the same row, then the row with `full` comes first
3699
+ if (order1 === order2) {
3700
+ var ret = a.full && ! b.full ? -1 : 1;
3701
+
3702
+ return side === 'bottom'
3703
+ ? ret * -1
3704
+ : ret;
3705
+ }
3619
3706
 
3620
3707
  return order2 - order1;
3621
3708
  } );
3622
-
3709
+
3710
+ // Invert for below the table
3623
3711
  if ( side === 'bottom' ) {
3624
- filtered.reverse();
3712
+ rows.reverse();
3625
3713
  }
3626
3714
 
3627
- // Split into rows
3628
- var rows = [];
3629
- for ( var i=0, ien=filtered.length ; i<ien ; i++ ) {
3630
- if ( filtered[i].val.full ) {
3631
- rows.push( { full: filtered[i].val.full } );
3632
- _layoutResolve( settings, rows[ rows.length - 1 ] );
3633
-
3634
- delete filtered[i].val.full;
3635
- }
3715
+ for (var row = 0; row<rows.length; row++) {
3716
+ delete rows[row].rowNum;
3636
3717
 
3637
- if ( Object.keys(filtered[i].val).length ) {
3638
- rows.push( filtered[i].val );
3639
- _layoutResolve( settings, rows[ rows.length - 1 ] );
3640
- }
3718
+ _layoutResolve(settings, rows[row]);
3641
3719
  }
3642
3720
 
3643
3721
  return rows;
@@ -3661,6 +3739,10 @@
3661
3739
  };
3662
3740
 
3663
3741
  var resolve = function ( item ) {
3742
+ if (! row[ item ]) {
3743
+ return;
3744
+ }
3745
+
3664
3746
  var line = row[ item ].contents;
3665
3747
 
3666
3748
  for ( var i=0, ien=line.length ; i<ien ; i++ ) {
@@ -3688,9 +3770,9 @@
3688
3770
  }
3689
3771
  };
3690
3772
 
3691
- $.each( row, function ( key ) {
3692
- resolve( key );
3693
- } );
3773
+ resolve('start');
3774
+ resolve('end');
3775
+ resolve('full');
3694
3776
  }
3695
3777
 
3696
3778
 
@@ -4023,6 +4105,17 @@
4023
4105
  _fnLog( oSettings, 0, error );
4024
4106
  }
4025
4107
 
4108
+ // Microsoft often wrap JSON as a string in another JSON object
4109
+ // Let's handle that automatically
4110
+ if (json.d && typeof json.d === 'string') {
4111
+ try {
4112
+ json = JSON.parse(json.d);
4113
+ }
4114
+ catch (e) {
4115
+ // noop
4116
+ }
4117
+ }
4118
+
4026
4119
  oSettings.json = json;
4027
4120
 
4028
4121
  _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true );
@@ -4099,11 +4192,11 @@
4099
4192
  else {
4100
4193
  // Object to extend the base settings
4101
4194
  oSettings.jqXHR = $.ajax( baseAjax );
4195
+ }
4102
4196
 
4103
- // Restore for next time around
4104
- if ( ajaxData ) {
4105
- ajax.data = ajaxData;
4106
- }
4197
+ // Restore for next time around
4198
+ if ( ajaxData ) {
4199
+ ajax.data = ajaxData;
4107
4200
  }
4108
4201
  }
4109
4202
 
@@ -4234,6 +4327,7 @@
4234
4327
  }
4235
4328
  settings.aiDisplay = settings.aiDisplayMaster.slice();
4236
4329
 
4330
+ _fnColumnTypes(settings);
4237
4331
  _fnDraw( settings, true );
4238
4332
  _fnInitComplete( settings );
4239
4333
  _fnProcessingDisplay( settings, false );
@@ -4328,10 +4422,6 @@
4328
4422
  {
4329
4423
  var columnsSearch = settings.aoPreSearchCols;
4330
4424
 
4331
- // Resolve any column types that are unknown due to addition or invalidation
4332
- // @todo As per sort - can this be moved into an event handler?
4333
- _fnColumnTypes( settings );
4334
-
4335
4425
  // In server-side processing all filtering is done by the server, so no point hanging around here
4336
4426
  if ( _fnDataSource( settings ) != 'ssp' )
4337
4427
  {
@@ -4407,7 +4497,7 @@
4407
4497
  // So the array reference doesn't break set the results into the
4408
4498
  // existing array
4409
4499
  displayRows.length = 0;
4410
- displayRows.push.apply(displayRows, rows);
4500
+ _fnArrayApply(displayRows, rows);
4411
4501
  }
4412
4502
  }
4413
4503
 
@@ -4636,66 +4726,90 @@
4636
4726
  */
4637
4727
  function _fnInitialise ( settings )
4638
4728
  {
4639
- var i, iAjaxStart=settings.iInitDisplayStart;
4729
+ var i;
4730
+ var init = settings.oInit;
4731
+ var deferLoading = settings.deferLoading;
4732
+ var dataSrc = _fnDataSource( settings );
4640
4733
 
4641
- /* Ensure that the table data is fully initialised */
4734
+ // Ensure that the table data is fully initialised
4642
4735
  if ( ! settings.bInitialised ) {
4643
4736
  setTimeout( function(){ _fnInitialise( settings ); }, 200 );
4644
4737
  return;
4645
4738
  }
4646
4739
 
4647
- /* Build and draw the header / footer for the table */
4740
+ // Build the header / footer for the table
4648
4741
  _fnBuildHead( settings, 'header' );
4649
4742
  _fnBuildHead( settings, 'footer' );
4650
- _fnDrawHead( settings, settings.aoHeader );
4651
- _fnDrawHead( settings, settings.aoFooter );
4652
4743
 
4653
- // Enable features
4654
- _fnAddOptionsHtml( settings );
4655
- _fnSortInit( settings );
4744
+ // Load the table's state (if needed) and then render around it and draw
4745
+ _fnLoadState( settings, init, function () {
4746
+ // Then draw the header / footer
4747
+ _fnDrawHead( settings, settings.aoHeader );
4748
+ _fnDrawHead( settings, settings.aoFooter );
4656
4749
 
4657
- _colGroup( settings );
4750
+ // Cache the paging start point, as the first redraw will reset it
4751
+ var iAjaxStart = settings.iInitDisplayStart
4658
4752
 
4659
- /* Okay to show that something is going on now */
4660
- _fnProcessingDisplay( settings, true );
4753
+ // Local data load
4754
+ // Check if there is data passing into the constructor
4755
+ if ( init.aaData ) {
4756
+ for ( i=0 ; i<init.aaData.length ; i++ ) {
4757
+ _fnAddData( settings, init.aaData[ i ] );
4758
+ }
4759
+ }
4760
+ else if ( deferLoading || dataSrc == 'dom' ) {
4761
+ // Grab the data from the page
4762
+ _fnAddTr( settings, $(settings.nTBody).children('tr') );
4763
+ }
4661
4764
 
4662
- _fnCallbackFire( settings, null, 'preInit', [settings], true );
4765
+ // Filter not yet applied - copy the display master
4766
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
4663
4767
 
4664
- // If there is default sorting required - let's do it. The sort function
4665
- // will do the drawing for us. Otherwise we draw the table regardless of the
4666
- // Ajax source - this allows the table to look initialised for Ajax sourcing
4667
- // data (show 'loading' message possibly)
4668
- _fnReDraw( settings );
4768
+ // Enable features
4769
+ _fnAddOptionsHtml( settings );
4770
+ _fnSortInit( settings );
4669
4771
 
4670
- var dataSrc = _fnDataSource( settings );
4772
+ _colGroup( settings );
4671
4773
 
4672
- // Server-side processing init complete is done by _fnAjaxUpdateDraw
4673
- if ( dataSrc != 'ssp' ) {
4674
- // if there is an ajax source load the data
4675
- if ( dataSrc == 'ajax' ) {
4676
- _fnBuildAjax( settings, {}, function(json) {
4677
- var aData = _fnAjaxDataSrc( settings, json );
4774
+ /* Okay to show that something is going on now */
4775
+ _fnProcessingDisplay( settings, true );
4678
4776
 
4679
- // Got the data - add it to the table
4680
- for ( i=0 ; i<aData.length ; i++ ) {
4681
- _fnAddData( settings, aData[i] );
4682
- }
4777
+ _fnCallbackFire( settings, null, 'preInit', [settings], true );
4683
4778
 
4684
- // Reset the init display for cookie saving. We've already done
4685
- // a filter, and therefore cleared it before. So we need to make
4686
- // it appear 'fresh'
4687
- settings.iInitDisplayStart = iAjaxStart;
4779
+ // If there is default sorting required - let's do it. The sort function
4780
+ // will do the drawing for us. Otherwise we draw the table regardless of the
4781
+ // Ajax source - this allows the table to look initialised for Ajax sourcing
4782
+ // data (show 'loading' message possibly)
4783
+ _fnReDraw( settings );
4688
4784
 
4689
- _fnReDraw( settings );
4690
- _fnProcessingDisplay( settings, false );
4785
+ // Server-side processing init complete is done by _fnAjaxUpdateDraw
4786
+ if ( dataSrc != 'ssp' || deferLoading ) {
4787
+ // if there is an ajax source load the data
4788
+ if ( dataSrc == 'ajax' ) {
4789
+ _fnBuildAjax( settings, {}, function(json) {
4790
+ var aData = _fnAjaxDataSrc( settings, json );
4791
+
4792
+ // Got the data - add it to the table
4793
+ for ( i=0 ; i<aData.length ; i++ ) {
4794
+ _fnAddData( settings, aData[i] );
4795
+ }
4796
+
4797
+ // Reset the init display for cookie saving. We've already done
4798
+ // a filter, and therefore cleared it before. So we need to make
4799
+ // it appear 'fresh'
4800
+ settings.iInitDisplayStart = iAjaxStart;
4801
+
4802
+ _fnReDraw( settings );
4803
+ _fnProcessingDisplay( settings, false );
4804
+ _fnInitComplete( settings );
4805
+ }, settings );
4806
+ }
4807
+ else {
4691
4808
  _fnInitComplete( settings );
4692
- }, settings );
4693
- }
4694
- else {
4695
- _fnInitComplete( settings );
4696
- _fnProcessingDisplay( settings, false );
4809
+ _fnProcessingDisplay( settings, false );
4810
+ }
4697
4811
  }
4698
- }
4812
+ } );
4699
4813
  }
4700
4814
 
4701
4815
 
@@ -4850,8 +4964,37 @@
4850
4964
  */
4851
4965
  function _fnProcessingDisplay ( settings, show )
4852
4966
  {
4967
+ // Ignore cases when we are still redrawing
4968
+ if (settings.bDrawing && show === false) {
4969
+ return;
4970
+ }
4971
+
4853
4972
  _fnCallbackFire( settings, null, 'processing', [settings, show] );
4854
4973
  }
4974
+
4975
+ /**
4976
+ * Show the processing element if an action takes longer than a given time
4977
+ *
4978
+ * @param {*} settings DataTables settings object
4979
+ * @param {*} enable Do (true) or not (false) async processing (local feature enablement)
4980
+ * @param {*} run Function to run
4981
+ */
4982
+ function _fnProcessingRun( settings, enable, run ) {
4983
+ if (! enable) {
4984
+ // Immediate execution, synchronous
4985
+ run();
4986
+ }
4987
+ else {
4988
+ _fnProcessingDisplay(settings, true);
4989
+
4990
+ // Allow the processing display to show if needed
4991
+ setTimeout(function () {
4992
+ run();
4993
+
4994
+ _fnProcessingDisplay(settings, false);
4995
+ }, 0);
4996
+ }
4997
+ }
4855
4998
  /**
4856
4999
  * Add any control elements for the table - specifically scrolling
4857
5000
  * @param {object} settings dataTables settings object
@@ -5078,21 +5221,44 @@
5078
5221
  // is because of Responsive which might remove `col` elements, knocking the alignment
5079
5222
  // of the indexes out.
5080
5223
  if (settings.aiDisplay.length) {
5081
- // Get the column sizes from the first row in the table
5082
- var colSizes = table.children('tbody').eq(0).children('tr').eq(0).children('th, td').map(function (vis) {
5083
- return {
5084
- idx: _fnVisibleToColumnIndex(settings, vis),
5085
- width: $(this).outerWidth()
5224
+ // Get the column sizes from the first row in the table. This should really be a
5225
+ // [].find, but it wasn't supported in Chrome until Sept 2015, and DT has 10 year
5226
+ // browser support
5227
+ var firstTr = null;
5228
+ var start = _fnDataSource( settings ) !== 'ssp'
5229
+ ? settings._iDisplayStart
5230
+ : 0;
5231
+
5232
+ for (i=start ; i<start + settings.aiDisplay.length ; i++) {
5233
+ var idx = settings.aiDisplay[i];
5234
+ var tr = settings.aoData[idx].nTr;
5235
+
5236
+ if (tr) {
5237
+ firstTr = tr;
5238
+ break;
5086
5239
  }
5087
- });
5240
+ }
5241
+
5242
+ if (firstTr) {
5243
+ var colSizes = $(firstTr).children('th, td').map(function (vis) {
5244
+ return {
5245
+ idx: _fnVisibleToColumnIndex(settings, vis),
5246
+ width: $(this).outerWidth()
5247
+ };
5248
+ });
5249
+
5250
+ // Check against what the colgroup > col is set to and correct if needed
5251
+ for (var i=0 ; i<colSizes.length ; i++) {
5252
+ var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0];
5253
+ var colWidth = colEl.style.width.replace('px', '');
5088
5254
 
5089
- // Check against what the colgroup > col is set to and correct if needed
5090
- for (var i=0 ; i<colSizes.length ; i++) {
5091
- var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0];
5092
- var colWidth = colEl.style.width.replace('px', '');
5255
+ if (colWidth !== colSizes[i].width) {
5256
+ colEl.style.width = colSizes[i].width + 'px';
5093
5257
 
5094
- if (colWidth !== colSizes[i].width) {
5095
- colEl.style.width = colSizes[i].width + 'px';
5258
+ if (scroll.sX) {
5259
+ colEl.style.minWidth = colSizes[i].width + 'px';
5260
+ }
5261
+ }
5096
5262
  }
5097
5263
  }
5098
5264
  }
@@ -5182,8 +5348,25 @@
5182
5348
  tableWidthAttr = table.getAttribute('width'), // from DOM element
5183
5349
  tableContainer = table.parentNode,
5184
5350
  i, column, columnIdx;
5351
+
5352
+ var styleWidth = table.style.width;
5353
+ var containerWidth = _fnWrapperWidth(settings);
5354
+
5355
+ // Don't re-run for the same width as the last time
5356
+ if (containerWidth === settings.containerWidth) {
5357
+ return false;
5358
+ }
5359
+
5360
+ settings.containerWidth = containerWidth;
5361
+
5362
+ // If there is no width applied as a CSS style or as an attribute, we assume that
5363
+ // the width is intended to be 100%, which is usually is in CSS, but it is very
5364
+ // difficult to correctly parse the rules to get the final result.
5365
+ if ( ! styleWidth && ! tableWidthAttr) {
5366
+ table.style.width = '100%';
5367
+ styleWidth = '100%';
5368
+ }
5185
5369
 
5186
- var styleWidth = table.style.width;
5187
5370
  if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
5188
5371
  tableWidthAttr = styleWidth;
5189
5372
  }
@@ -5232,6 +5415,8 @@
5232
5415
  // browser will collapse it. If this width is smaller than the
5233
5416
  // width the column requires, then it will have no effect
5234
5417
  if ( scrollX ) {
5418
+ this.style.minWidth = width;
5419
+
5235
5420
  $( this ).append( $('<div/>').css( {
5236
5421
  width: width,
5237
5422
  margin: 0,
@@ -5300,15 +5485,15 @@
5300
5485
 
5301
5486
  // If there is no width attribute or style, then allow the table to
5302
5487
  // collapse
5303
- if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
5304
- tmpTable.width( tableContainer.clientWidth );
5488
+ if ( tmpTable.outerWidth() < tableContainer.clientWidth && tableWidthAttr ) {
5489
+ tmpTable.outerWidth( tableContainer.clientWidth );
5305
5490
  }
5306
5491
  }
5307
5492
  else if ( scrollY ) {
5308
- tmpTable.width( tableContainer.clientWidth );
5493
+ tmpTable.outerWidth( tableContainer.clientWidth );
5309
5494
  }
5310
5495
  else if ( tableWidthAttr ) {
5311
- tmpTable.width( tableWidthAttr );
5496
+ tmpTable.outerWidth( tableWidthAttr );
5312
5497
  }
5313
5498
 
5314
5499
  // Get the width of each column in the constructed table
@@ -5341,20 +5526,64 @@
5341
5526
  }
5342
5527
 
5343
5528
  if ( (tableWidthAttr || scrollX) && ! settings._reszEvt ) {
5344
- var bindResize = function () {
5345
- $(window).on('resize.DT-'+settings.sInstance, DataTable.util.throttle( function () {
5346
- if (! settings.bDestroying) {
5347
- _fnAdjustColumnSizing( settings );
5529
+ var resize = DataTable.util.throttle( function () {
5530
+ var newWidth = _fnWrapperWidth(settings);
5531
+
5532
+ // Don't do it if destroying or the container width is 0
5533
+ if (! settings.bDestroying && newWidth !== 0) {
5534
+ _fnAdjustColumnSizing( settings );
5535
+ }
5536
+ } );
5537
+
5538
+ // For browsers that support it (~2020 onwards for wide support) we can watch for the
5539
+ // container changing width.
5540
+ if (window.ResizeObserver) {
5541
+ // This is a tricky beast - if the element is visible when `.observe()` is called,
5542
+ // then the callback is immediately run. Which we don't want. If the element isn't
5543
+ // visible, then it isn't run, but we want it to run when it is then made visible.
5544
+ // This flag allows the above to be satisfied.
5545
+ var first = $(settings.nTableWrapper).is(':visible');
5546
+
5547
+ // Use an empty div to attach the observer so it isn't impacted by height changes
5548
+ var resizer = $('<div>')
5549
+ .css({
5550
+ width: '100%',
5551
+ height: 0
5552
+ })
5553
+ .addClass('dt-autosize')
5554
+ .appendTo(settings.nTableWrapper);
5555
+
5556
+ settings.resizeObserver = new ResizeObserver(function (e) {
5557
+ if (first) {
5558
+ first = false;
5348
5559
  }
5349
- } ) );
5350
- };
5560
+ else {
5561
+ resize();
5562
+ }
5563
+ });
5351
5564
 
5352
- bindResize();
5565
+ settings.resizeObserver.observe(resizer[0]);
5566
+ }
5567
+ else {
5568
+ // For old browsers, the best we can do is listen for a window resize
5569
+ $(window).on('resize.DT-'+settings.sInstance, resize);
5570
+ }
5353
5571
 
5354
5572
  settings._reszEvt = true;
5355
5573
  }
5356
5574
  }
5357
5575
 
5576
+ /**
5577
+ * Get the width of the DataTables wrapper element
5578
+ *
5579
+ * @param {*} settings DataTables settings object
5580
+ * @returns Width
5581
+ */
5582
+ function _fnWrapperWidth(settings) {
5583
+ return $(settings.nTableWrapper).is(':visible')
5584
+ ? $(settings.nTableWrapper).width()
5585
+ : 0;
5586
+ }
5358
5587
 
5359
5588
  /**
5360
5589
  * Get the maximum strlen for each data column
@@ -5497,22 +5726,16 @@
5497
5726
  }
5498
5727
 
5499
5728
  if (run) {
5500
- _fnProcessingDisplay( settings, true );
5501
-
5502
- // Allow the processing display to show
5503
- setTimeout( function () {
5729
+ _fnProcessingRun(settings, true, function () {
5504
5730
  _fnSort( settings );
5505
5731
  _fnSortDisplay( settings, settings.aiDisplay );
5506
5732
 
5507
- // Sort processing done - redraw has its own processing display
5508
- _fnProcessingDisplay( settings, false );
5509
-
5510
5733
  _fnReDraw( settings, false, false );
5511
5734
 
5512
5735
  if (callback) {
5513
5736
  callback();
5514
5737
  }
5515
- }, 0);
5738
+ });
5516
5739
  }
5517
5740
  }
5518
5741
  } );
@@ -5671,15 +5894,14 @@
5671
5894
  displayMaster = oSettings.aiDisplayMaster,
5672
5895
  aSort;
5673
5896
 
5674
- // Resolve any column types that are unknown due to addition or invalidation
5675
- // @todo Can this be moved into a 'data-ready' handler which is called when
5676
- // data is going to be used in the table?
5677
- _fnColumnTypes( oSettings );
5897
+ // Make sure the columns all have types defined
5898
+ _fnColumnTypes(oSettings);
5678
5899
 
5679
5900
  // Allow a specific column to be sorted, which will _not_ alter the display
5680
5901
  // master
5681
5902
  if (col !== undefined) {
5682
5903
  var srcCol = oSettings.aoColumns[col];
5904
+
5683
5905
  aSort = [{
5684
5906
  src: col,
5685
5907
  col: col,
@@ -5712,7 +5934,7 @@
5712
5934
 
5713
5935
  // If the first sort is desc, then reverse the array to preserve original
5714
5936
  // order, just in reverse
5715
- if (aSort.length && aSort[0].dir === 'desc') {
5937
+ if (aSort.length && aSort[0].dir === 'desc' && oSettings.orderDescReverse) {
5716
5938
  aiOrig.reverse();
5717
5939
  }
5718
5940
 
@@ -5782,6 +6004,7 @@
5782
6004
  if (col === undefined) {
5783
6005
  // Tell the draw function that we have sorted the data
5784
6006
  oSettings.bSorted = true;
6007
+ oSettings.sortDetails = aSort;
5785
6008
 
5786
6009
  _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] );
5787
6010
  }
@@ -5973,15 +6196,26 @@
5973
6196
  return;
5974
6197
  }
5975
6198
 
6199
+ // Sort state saving uses [[idx, order]] structure.
6200
+ var sorting = [];
6201
+ _fnSortResolve(settings, sorting, settings.aaSorting );
6202
+
5976
6203
  /* Store the interesting variables */
6204
+ var columns = settings.aoColumns;
5977
6205
  var state = {
5978
6206
  time: +new Date(),
5979
6207
  start: settings._iDisplayStart,
5980
6208
  length: settings._iDisplayLength,
5981
- order: $.extend( true, [], settings.aaSorting ),
6209
+ order: sorting.map(function (sort) {
6210
+ // If a column name is available, use it
6211
+ return columns[sort[0]] && columns[sort[0]].sName
6212
+ ? [ columns[sort[0]].sName, sort[1] ]
6213
+ : sort.slice();
6214
+ } ),
5982
6215
  search: $.extend({}, settings.oPreviousSearch),
5983
6216
  columns: settings.aoColumns.map( function ( col, i ) {
5984
6217
  return {
6218
+ name: col.sName,
5985
6219
  visible: col.bVisible,
5986
6220
  search: $.extend({}, settings.aoPreSearchCols[i])
5987
6221
  };
@@ -6029,6 +6263,8 @@
6029
6263
  function _fnImplementState ( settings, s, callback) {
6030
6264
  var i, ien;
6031
6265
  var columns = settings.aoColumns;
6266
+ var currentNames = _pluck(settings.aoColumns, 'sName');
6267
+
6032
6268
  settings._bLoadingState = true;
6033
6269
 
6034
6270
  // When StateRestore was introduced the state could now be implemented at any time
@@ -6058,13 +6294,6 @@
6058
6294
  return;
6059
6295
  }
6060
6296
 
6061
- // Number of columns have changed - all bets are off, no restore of settings
6062
- if ( s.columns && columns.length !== s.columns.length ) {
6063
- settings._bLoadingState = false;
6064
- callback();
6065
- return;
6066
- }
6067
-
6068
6297
  // Store the saved state so it might be accessed at any time
6069
6298
  settings.oLoadedState = $.extend( true, {}, s );
6070
6299
 
@@ -6083,8 +6312,7 @@
6083
6312
  }
6084
6313
  }
6085
6314
 
6086
- // Restore key features - todo - for 1.11 this needs to be done by
6087
- // subscribed events
6315
+ // Restore key features
6088
6316
  if ( s.start !== undefined ) {
6089
6317
  if(api === null) {
6090
6318
  settings._iDisplayStart = s.start;
@@ -6099,10 +6327,23 @@
6099
6327
  if ( s.order !== undefined ) {
6100
6328
  settings.aaSorting = [];
6101
6329
  $.each( s.order, function ( i, col ) {
6102
- settings.aaSorting.push( col[0] >= columns.length ?
6103
- [ 0, col[1] ] :
6104
- col
6105
- );
6330
+ var set = [ col[0], col[1] ];
6331
+
6332
+ // A column name was stored and should be used for restore
6333
+ if (typeof col[0] === 'string') {
6334
+ var idx = currentNames.indexOf(col[0]);
6335
+
6336
+ // Find the name from the current list of column names, or fallback to index 0
6337
+ set[0] = idx >= 0
6338
+ ? idx
6339
+ : 0;
6340
+ }
6341
+ else if (set[0] >= columns.length) {
6342
+ // If a column name, but it is out of bounds, set to 0
6343
+ set[0] = 0;
6344
+ }
6345
+
6346
+ settings.aaSorting.push(set);
6106
6347
  } );
6107
6348
  }
6108
6349
 
@@ -6113,31 +6354,65 @@
6113
6354
 
6114
6355
  // Columns
6115
6356
  if ( s.columns ) {
6116
- for ( i=0, ien=s.columns.length ; i<ien ; i++ ) {
6117
- var col = s.columns[i];
6118
-
6119
- // Visibility
6120
- if ( col.visible !== undefined ) {
6121
- // If the api is defined, the table has been initialised so we need to use it rather than internal settings
6122
- if (api) {
6123
- // Don't redraw the columns on every iteration of this loop, we will do this at the end instead
6124
- api.column(i).visible(col.visible, false);
6357
+ var set = s.columns;
6358
+ var incoming = _pluck(s.columns, 'name');
6359
+
6360
+ // Check if it is a 2.2 style state object with a `name` property for the columns, and if
6361
+ // the name was defined. If so, then create a new array that will map the state object
6362
+ // given, to the current columns (don't bother if they are already matching tho).
6363
+ if (incoming.join('').length && incoming.join('') !== currentNames.join('')) {
6364
+ set = [];
6365
+
6366
+ // For each column, try to find the name in the incoming array
6367
+ for (i=0 ; i<currentNames.length ; i++) {
6368
+ if (currentNames[i] != '') {
6369
+ var idx = incoming.indexOf(currentNames[i]);
6370
+
6371
+ if (idx >= 0) {
6372
+ set.push(s.columns[idx]);
6373
+ }
6374
+ else {
6375
+ // No matching column name in the state's columns, so this might be a new
6376
+ // column and thus can't have a state already.
6377
+ set.push({});
6378
+ }
6125
6379
  }
6126
6380
  else {
6127
- columns[i].bVisible = col.visible;
6381
+ // If no name, but other columns did have a name, then there is no knowing
6382
+ // where this one came from originally so it can't be restored.
6383
+ set.push({});
6384
+ }
6385
+ }
6386
+ }
6387
+
6388
+ // If the number of columns to restore is different from current, then all bets are off.
6389
+ if (set.length === columns.length) {
6390
+ for ( i=0, ien=set.length ; i<ien ; i++ ) {
6391
+ var col = set[i];
6392
+
6393
+ // Visibility
6394
+ if ( col.visible !== undefined ) {
6395
+ // If the api is defined, the table has been initialised so we need to use it rather than internal settings
6396
+ if (api) {
6397
+ // Don't redraw the columns on every iteration of this loop, we will do this at the end instead
6398
+ api.column(i).visible(col.visible, false);
6399
+ }
6400
+ else {
6401
+ columns[i].bVisible = col.visible;
6402
+ }
6403
+ }
6404
+
6405
+ // Search
6406
+ if ( col.search !== undefined ) {
6407
+ $.extend( settings.aoPreSearchCols[i], col.search );
6128
6408
  }
6129
6409
  }
6130
6410
 
6131
- // Search
6132
- if ( col.search !== undefined ) {
6133
- $.extend( settings.aoPreSearchCols[i], col.search );
6411
+ // If the api is defined then we need to adjust the columns once the visibility has been changed
6412
+ if (api) {
6413
+ api.columns.adjust();
6134
6414
  }
6135
6415
  }
6136
-
6137
- // If the api is defined then we need to adjust the columns once the visibility has been changed
6138
- if (api) {
6139
- api.columns.adjust();
6140
- }
6141
6416
  }
6142
6417
 
6143
6418
  settings._bLoadingState = false;
@@ -6454,6 +6729,30 @@
6454
6729
  replace(/_ENTRIES-TOTAL_/g, settings.api.i18n('entries', '', vis) );
6455
6730
  }
6456
6731
 
6732
+ /**
6733
+ * Add elements to an array as quickly as possible, but stack stafe.
6734
+ *
6735
+ * @param {*} arr Array to add the data to
6736
+ * @param {*} data Data array that is to be added
6737
+ * @returns
6738
+ */
6739
+ function _fnArrayApply(arr, data) {
6740
+ if (! data) {
6741
+ return;
6742
+ }
6743
+
6744
+ // Chrome can throw a max stack error if apply is called with
6745
+ // too large an array, but apply is faster.
6746
+ if (data.length < 10000) {
6747
+ arr.push.apply(arr, data);
6748
+ }
6749
+ else {
6750
+ for (i=0 ; i<data.length ; i++) {
6751
+ arr.push(data[i]);
6752
+ }
6753
+ }
6754
+ }
6755
+
6457
6756
 
6458
6757
 
6459
6758
  /**
@@ -6622,6 +6921,7 @@
6622
6921
  return new _Api( context, data );
6623
6922
  }
6624
6923
 
6924
+ var i;
6625
6925
  var settings = [];
6626
6926
  var ctxSettings = function ( o ) {
6627
6927
  var a = _toSettings( o );
@@ -6631,7 +6931,7 @@
6631
6931
  };
6632
6932
 
6633
6933
  if ( Array.isArray( context ) ) {
6634
- for ( var i=0, ien=context.length ; i<ien ; i++ ) {
6934
+ for ( i=0 ; i<context.length ; i++ ) {
6635
6935
  ctxSettings( context[i] );
6636
6936
  }
6637
6937
  }
@@ -6645,9 +6945,7 @@
6645
6945
  : settings;
6646
6946
 
6647
6947
  // Initial data
6648
- if ( data ) {
6649
- this.push.apply(this, data);
6650
- }
6948
+ _fnArrayApply(this, data);
6651
6949
 
6652
6950
  // selector
6653
6951
  this.selector = {
@@ -7028,7 +7326,7 @@
7028
7326
  selector.forEach(function (sel) {
7029
7327
  var inner = __table_selector(sel, a);
7030
7328
 
7031
- result.push.apply(result, inner);
7329
+ _fnArrayApply(result, inner);
7032
7330
  });
7033
7331
 
7034
7332
  return result.filter( function (item) {
@@ -7529,7 +7827,7 @@
7529
7827
  // Reduce the API instance to the first item found
7530
7828
  var _selector_first = function ( old )
7531
7829
  {
7532
- let inst = new _Api(old.context[0]);
7830
+ var inst = new _Api(old.context[0]);
7533
7831
 
7534
7832
  // Use a push rather than passing to the constructor, since it will
7535
7833
  // merge arrays down automatically, which isn't what is wanted here
@@ -7882,7 +8180,7 @@
7882
8180
  // Return an Api.rows() extended instance, so rows().nodes() etc can be used
7883
8181
  var modRows = this.rows( -1 );
7884
8182
  modRows.pop();
7885
- modRows.push.apply(modRows, newRows);
8183
+ _fnArrayApply(modRows, newRows);
7886
8184
 
7887
8185
  return modRows;
7888
8186
  } );
@@ -8336,8 +8634,10 @@
8336
8634
  switch( match[2] ) {
8337
8635
  case 'visIdx':
8338
8636
  case 'visible':
8339
- if (match[1]) {
8637
+ // Selector is a column index
8638
+ if (match[1] && match[1].match(/^\d+$/)) {
8340
8639
  var idx = parseInt( match[1], 10 );
8640
+
8341
8641
  // Visible index given, convert to column index
8342
8642
  if ( idx < 0 ) {
8343
8643
  // Counting from the right
@@ -8350,9 +8650,19 @@
8350
8650
  return [ _fnVisibleToColumnIndex( settings, idx ) ];
8351
8651
  }
8352
8652
 
8353
- // `:visible` on its own
8354
- return columns.map( function (col, i) {
8355
- return col.bVisible ? i : null;
8653
+ return columns.map( function (col, idx) {
8654
+ // Not visible, can't match
8655
+ if (! col.bVisible) {
8656
+ return null;
8657
+ }
8658
+
8659
+ // Selector
8660
+ if (match[1]) {
8661
+ return $(nodes[idx]).filter(match[1]).length > 0 ? idx : null;
8662
+ }
8663
+
8664
+ // `:visible` on its own
8665
+ return idx;
8356
8666
  } );
8357
8667
 
8358
8668
  case 'name':
@@ -8383,7 +8693,10 @@
8383
8693
  .map( function () {
8384
8694
  return _fnColumnsFromHeader( this ); // `nodes` is column index complete and in order
8385
8695
  } )
8386
- .toArray();
8696
+ .toArray()
8697
+ .sort(function (a, b) {
8698
+ return a - b;
8699
+ });
8387
8700
 
8388
8701
  if ( jqResult.length || ! s.nodeName ) {
8389
8702
  return jqResult;
@@ -8637,6 +8950,10 @@
8637
8950
 
8638
8951
  _api_register( 'columns.adjust()', function () {
8639
8952
  return this.iterator( 'table', function ( settings ) {
8953
+ // Force a column sizing to happen with a manual call - otherwise it can skip
8954
+ // if the size hasn't changed
8955
+ settings.containerWidth = -1;
8956
+
8640
8957
  _fnAdjustColumnSizing( settings );
8641
8958
  }, 1 );
8642
8959
  } );
@@ -9202,24 +9519,81 @@
9202
9519
  } );
9203
9520
  } );
9204
9521
 
9522
+ // Can be assigned in DateTable.use() - note luxon and moment vars are in helpers.js
9523
+ var __bootstrap;
9524
+ var __foundation;
9525
+
9205
9526
  /**
9206
- * Set the jQuery or window object to be used by DataTables
9207
- *
9208
- * @param {*} module Library / container object
9209
- * @param {string} [type] Library or container type `lib`, `win` or `datetime`.
9210
- * If not provided, automatic detection is attempted.
9211
- */
9212
- DataTable.use = function (module, type) {
9213
- if (type === 'lib' || module.fn) {
9527
+ * Set the libraries that DataTables uses, or the global objects.
9528
+ * Note that the arguments can be either way around (legacy support)
9529
+ * and the second is optional. See docs.
9530
+ */
9531
+ DataTable.use = function (arg1, arg2) {
9532
+ // Reverse arguments for legacy support
9533
+ var module = typeof arg1 === 'string'
9534
+ ? arg2
9535
+ : arg1;
9536
+ var type = typeof arg2 === 'string'
9537
+ ? arg2
9538
+ : arg1;
9539
+
9540
+ // Getter
9541
+ if (module === undefined && typeof type === 'string') {
9542
+ switch (type) {
9543
+ case 'lib':
9544
+ case 'jq':
9545
+ return $;
9546
+
9547
+ case 'win':
9548
+ return window;
9549
+
9550
+ case 'datetime':
9551
+ return DataTable.DateTime;
9552
+
9553
+ case 'luxon':
9554
+ return __luxon;
9555
+
9556
+ case 'moment':
9557
+ return __moment;
9558
+
9559
+ case 'bootstrap':
9560
+ // Use local if set, otherwise try window, which could be undefined
9561
+ return __bootstrap || window.bootstrap;
9562
+
9563
+ case 'foundation':
9564
+ // Ditto
9565
+ return __foundation || window.Foundation;
9566
+
9567
+ default:
9568
+ return null;
9569
+ }
9570
+ }
9571
+
9572
+ // Setter
9573
+ if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) {
9214
9574
  $ = module;
9215
9575
  }
9216
- else if (type == 'win' || module.document) {
9576
+ else if (type === 'win' || (module && module.document)) {
9217
9577
  window = module;
9218
9578
  document = module.document;
9219
9579
  }
9220
- else if (type === 'datetime' || module.type === 'DateTime') {
9580
+ else if (type === 'datetime' || (module && module.type === 'DateTime')) {
9221
9581
  DataTable.DateTime = module;
9222
9582
  }
9583
+ else if (type === 'luxon' || (module && module.FixedOffsetZone)) {
9584
+ __luxon = module;
9585
+ }
9586
+ else if (type === 'moment' || (module && module.isMoment)) {
9587
+ __moment = module;
9588
+ }
9589
+ else if (type === 'bootstrap' || (module && module.Modal && module.Modal.NAME === 'modal'))
9590
+ {
9591
+ // This is currently for BS5 only. BS3/4 attach to jQuery, so no need to use `.use()`
9592
+ __bootstrap = module;
9593
+ }
9594
+ else if (type === 'foundation' || (module && module.Reveal)) {
9595
+ __foundation = module;
9596
+ }
9223
9597
  }
9224
9598
 
9225
9599
  /**
@@ -9471,12 +9845,14 @@
9471
9845
  // Function to run either once the table becomes ready or
9472
9846
  // immediately if it is already ready.
9473
9847
  return this.tables().every(function () {
9848
+ var api = this;
9849
+
9474
9850
  if (this.context[0]._bInitComplete) {
9475
- fn.call(this);
9851
+ fn.call(api);
9476
9852
  }
9477
9853
  else {
9478
- this.on('init', function () {
9479
- fn.call(this);
9854
+ this.on('init.dt.DT', function () {
9855
+ fn.call(api);
9480
9856
  });
9481
9857
  }
9482
9858
  } );
@@ -9510,6 +9886,11 @@
9510
9886
  new _Api( settings ).columns().visible( true );
9511
9887
  }
9512
9888
 
9889
+ // Container width change listener
9890
+ if (settings.resizeObserver) {
9891
+ settings.resizeObserver.disconnect();
9892
+ }
9893
+
9513
9894
  // Blitz all `DT` namespaced events (these are internal events, the
9514
9895
  // lowercase, `dt` events are user subscribed and they are responsible
9515
9896
  // for removing them
@@ -9527,20 +9908,37 @@
9527
9908
  jqTable.append( tfoot );
9528
9909
  }
9529
9910
 
9911
+ // Clean up the header
9912
+ $(thead).find('span.dt-column-order').remove();
9913
+ $(thead).find('span.dt-column-title').each(function () {
9914
+ var title = $(this).html();
9915
+ $(this).parent().append(title);
9916
+ $(this).remove();
9917
+ });
9918
+
9530
9919
  settings.colgroup.remove();
9531
9920
 
9532
9921
  settings.aaSorting = [];
9533
9922
  settings.aaSortingFixed = [];
9534
9923
  _fnSortingClasses( settings );
9535
9924
 
9925
+ $(jqTable).find('th, td').removeClass(
9926
+ $.map(DataTable.ext.type.className, function (v) {
9927
+ return v;
9928
+ }).join(' ')
9929
+ );
9930
+
9536
9931
  $('th, td', thead)
9537
9932
  .removeClass(
9933
+ orderClasses.none + ' ' +
9538
9934
  orderClasses.canAsc + ' ' +
9539
9935
  orderClasses.canDesc + ' ' +
9540
9936
  orderClasses.isAsc + ' ' +
9541
9937
  orderClasses.isDesc
9542
9938
  )
9543
- .css('width', '');
9939
+ .css('width', '')
9940
+ .removeAttr('data-dt-column')
9941
+ .removeAttr('aria-sort');
9544
9942
 
9545
9943
  // Add the TR elements back into the table in their original order
9546
9944
  jqTbody.children().detach();
@@ -9628,7 +10026,7 @@
9628
10026
  * @type string
9629
10027
  * @default Version number
9630
10028
  */
9631
- DataTable.version = "2.0.8";
10029
+ DataTable.version = "2.2.2";
9632
10030
 
9633
10031
  /**
9634
10032
  * Private data store, containing all of the settings objects that are
@@ -10473,7 +10871,8 @@
10473
10871
  first: 'First',
10474
10872
  last: 'Last',
10475
10873
  next: 'Next',
10476
- previous: 'Previous'
10874
+ previous: 'Previous',
10875
+ number: ''
10477
10876
  }
10478
10877
  },
10479
10878
 
@@ -10653,6 +11052,10 @@
10653
11052
  },
10654
11053
 
10655
11054
 
11055
+ /** The initial data order is reversed when `desc` ordering */
11056
+ orderDescReverse: true,
11057
+
11058
+
10656
11059
  /**
10657
11060
  * This parameter allows you to have define the global filtering state at
10658
11061
  * initialisation time. As an object the `search` parameter must be
@@ -10701,7 +11104,7 @@
10701
11104
  * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
10702
11105
  * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
10703
11106
  */
10704
- "sPaginationType": "full_numbers",
11107
+ "sPaginationType": "",
10705
11108
 
10706
11109
 
10707
11110
  /**
@@ -10771,7 +11174,13 @@
10771
11174
  /**
10772
11175
  * Caption value
10773
11176
  */
10774
- "caption": null
11177
+ "caption": null,
11178
+
11179
+
11180
+ /**
11181
+ * For server-side processing - use the data from the DOM for the first draw
11182
+ */
11183
+ iDeferLoading: null
10775
11184
  };
10776
11185
 
10777
11186
  _fnHungarianMap( DataTable.defaults );
@@ -11714,7 +12123,19 @@
11714
12123
 
11715
12124
  captionNode: null,
11716
12125
 
11717
- colgroup: null
12126
+ colgroup: null,
12127
+
12128
+ /** Delay loading of data */
12129
+ deferLoading: null,
12130
+
12131
+ /** Allow auto type detection */
12132
+ typeDetect: true,
12133
+
12134
+ /** ResizeObserver for the container div */
12135
+ resizeObserver: null,
12136
+
12137
+ /** Keep a record of the last size of the container, so we can skip duplicates */
12138
+ containerWidth: -1
11718
12139
  };
11719
12140
 
11720
12141
  /**
@@ -11738,7 +12159,7 @@
11738
12159
  },
11739
12160
 
11740
12161
  full: function () {
11741
- return [ 'first', 'previous', 'next', 'last' ];
12162
+ return [ 'first', 'previous', 'next', 'last' ];
11742
12163
  },
11743
12164
 
11744
12165
  numbers: function () {
@@ -11752,11 +12173,11 @@
11752
12173
  full_numbers: function () {
11753
12174
  return [ 'first', 'previous', 'numbers', 'next', 'last' ];
11754
12175
  },
11755
-
12176
+
11756
12177
  first_last: function () {
11757
12178
  return ['first', 'last'];
11758
12179
  },
11759
-
12180
+
11760
12181
  first_last_numbers: function () {
11761
12182
  return ['first', 'numbers', 'last'];
11762
12183
  },
@@ -11838,44 +12259,62 @@
11838
12259
  * to make working with DataTables a little bit easier.
11839
12260
  */
11840
12261
 
11841
- function __mldFnName(name) {
11842
- return name.replace(/[\W]/g, '_')
11843
- }
11844
-
11845
- // Common logic for moment, luxon or a date action
11846
- function __mld( dt, momentFn, luxonFn, dateFn, arg1 ) {
11847
- if (window.moment) {
11848
- return dt[momentFn]( arg1 );
12262
+ /**
12263
+ * Common logic for moment, luxon or a date action.
12264
+ *
12265
+ * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again
12266
+ */
12267
+ function __mld( dtLib, momentFn, luxonFn, dateFn, arg1 ) {
12268
+ if (__moment) {
12269
+ return dtLib[momentFn]( arg1 );
11849
12270
  }
11850
- else if (window.luxon) {
11851
- return dt[luxonFn]( arg1 );
12271
+ else if (__luxon) {
12272
+ return dtLib[luxonFn]( arg1 );
11852
12273
  }
11853
12274
 
11854
- return dateFn ? dt[dateFn]( arg1 ) : dt;
12275
+ return dateFn ? dtLib[dateFn]( arg1 ) : dtLib;
11855
12276
  }
11856
12277
 
11857
12278
 
11858
12279
  var __mlWarning = false;
12280
+ var __luxon; // Can be assigned in DateTable.use()
12281
+ var __moment; // Can be assigned in DateTable.use()
12282
+
12283
+ /**
12284
+ *
12285
+ */
12286
+ function resolveWindowLibs() {
12287
+ if (window.luxon && ! __luxon) {
12288
+ __luxon = window.luxon;
12289
+ }
12290
+
12291
+ if (window.moment && ! __moment) {
12292
+ __moment = window.moment;
12293
+ }
12294
+ }
12295
+
11859
12296
  function __mldObj (d, format, locale) {
11860
12297
  var dt;
11861
12298
 
11862
- if (window.moment) {
11863
- dt = window.moment.utc( d, format, locale, true );
12299
+ resolveWindowLibs();
12300
+
12301
+ if (__moment) {
12302
+ dt = __moment.utc( d, format, locale, true );
11864
12303
 
11865
12304
  if (! dt.isValid()) {
11866
12305
  return null;
11867
12306
  }
11868
12307
  }
11869
- else if (window.luxon) {
12308
+ else if (__luxon) {
11870
12309
  dt = format && typeof d === 'string'
11871
- ? window.luxon.DateTime.fromFormat( d, format )
11872
- : window.luxon.DateTime.fromISO( d );
12310
+ ? __luxon.DateTime.fromFormat( d, format )
12311
+ : __luxon.DateTime.fromISO( d );
11873
12312
 
11874
12313
  if (! dt.isValid) {
11875
12314
  return null;
11876
12315
  }
11877
12316
 
11878
- dt.setLocale(locale);
12317
+ dt = dt.setLocale(locale);
11879
12318
  }
11880
12319
  else if (! format) {
11881
12320
  // No format given, must be ISO
@@ -11914,11 +12353,11 @@
11914
12353
  from = null;
11915
12354
  }
11916
12355
 
11917
- var typeName = 'datetime' + (to ? '-' + __mldFnName(to) : '');
12356
+ var typeName = 'datetime' + (to ? '-' + to : '');
11918
12357
 
11919
12358
  // Add type detection and sorting specific to this date format - we need to be able to identify
11920
12359
  // date type columns as such, rather than as numbers in extensions. Hence the need for this.
11921
- if (! DataTable.ext.type.order[typeName]) {
12360
+ if (! DataTable.ext.type.order[typeName + '-pre']) {
11922
12361
  DataTable.type(typeName, {
11923
12362
  detect: function (d) {
11924
12363
  // The renderer will give the value to type detect as the type!
@@ -12017,7 +12456,7 @@
12017
12456
 
12018
12457
  // Formatted date time detection - use by declaring the formats you are going to use
12019
12458
  DataTable.datetime = function ( format, locale ) {
12020
- var typeName = 'datetime-detect-' + __mldFnName(format);
12459
+ var typeName = 'datetime-' + format;
12021
12460
 
12022
12461
  if (! locale) {
12023
12462
  locale = 'en';
@@ -12157,7 +12596,7 @@
12157
12596
  return {
12158
12597
  className: _extTypes.className[name],
12159
12598
  detect: _extTypes.detect.find(function (fn) {
12160
- return fn.name === name;
12599
+ return fn._name === name;
12161
12600
  }),
12162
12601
  order: {
12163
12602
  pre: _extTypes.order[name + '-pre'],
@@ -12172,27 +12611,20 @@
12172
12611
  var setProp = function(prop, propVal) {
12173
12612
  _extTypes[prop][name] = propVal;
12174
12613
  };
12175
- var setDetect = function (fn) {
12176
- // Wrap to allow the function to return `true` rather than
12177
- // specifying the type name.
12178
- var cb = function (d, s) {
12179
- var ret = fn(d, s);
12180
-
12181
- return ret === true
12182
- ? name
12183
- : ret;
12184
- };
12185
- Object.defineProperty(cb, "name", {value: name});
12614
+ var setDetect = function (detect) {
12615
+ // `detect` can be a function or an object - we set a name
12616
+ // property for either - that is used for the detection
12617
+ Object.defineProperty(detect, "_name", {value: name});
12186
12618
 
12187
- var idx = _extTypes.detect.findIndex(function (fn) {
12188
- return fn.name === name;
12619
+ var idx = _extTypes.detect.findIndex(function (item) {
12620
+ return item._name === name;
12189
12621
  });
12190
12622
 
12191
12623
  if (idx === -1) {
12192
- _extTypes.detect.unshift(cb);
12624
+ _extTypes.detect.unshift(detect);
12193
12625
  }
12194
12626
  else {
12195
- _extTypes.detect.splice(idx, 1, cb);
12627
+ _extTypes.detect.splice(idx, 1, detect);
12196
12628
  }
12197
12629
  };
12198
12630
  var setOrder = function (obj) {
@@ -12248,10 +12680,30 @@
12248
12680
  // Get a list of types
12249
12681
  DataTable.types = function () {
12250
12682
  return _extTypes.detect.map(function (fn) {
12251
- return fn.name;
12683
+ return fn._name;
12252
12684
  });
12253
12685
  };
12254
12686
 
12687
+ var __diacriticSort = function (a, b) {
12688
+ a = a !== null && a !== undefined ? a.toString().toLowerCase() : '';
12689
+ b = b !== null && b !== undefined ? b.toString().toLowerCase() : '';
12690
+
12691
+ // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old
12692
+ // Safari and thus can disable this check
12693
+ // eslint-disable-next-line compat/compat
12694
+ return a.localeCompare(b, navigator.languages[0] || navigator.language, {
12695
+ numeric: true,
12696
+ ignorePunctuation: true,
12697
+ });
12698
+ }
12699
+
12700
+ var __diacriticHtmlSort = function (a, b) {
12701
+ a = _stripHtml(a);
12702
+ b = _stripHtml(b);
12703
+
12704
+ return __diacriticSort(a, b);
12705
+ }
12706
+
12255
12707
  //
12256
12708
  // Built in data types
12257
12709
  //
@@ -12264,7 +12716,7 @@
12264
12716
  pre: function ( a ) {
12265
12717
  // This is a little complex, but faster than always calling toString,
12266
12718
  // http://jsperf.com/tostring-v-check
12267
- return _empty(a) ?
12719
+ return _empty(a) && typeof a !== 'boolean' ?
12268
12720
  '' :
12269
12721
  typeof a === 'string' ?
12270
12722
  a.toLowerCase() :
@@ -12276,11 +12728,38 @@
12276
12728
  search: _filterString(false, true)
12277
12729
  });
12278
12730
 
12731
+ DataTable.type('string-utf8', {
12732
+ detect: {
12733
+ allOf: function ( d ) {
12734
+ return true;
12735
+ },
12736
+ oneOf: function ( d ) {
12737
+ // At least one data point must contain a non-ASCII character
12738
+ // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-)
12739
+ // this data type won't be supported.
12740
+ // eslint-disable-next-line compat/compat
12741
+ return ! _empty( d ) && navigator.languages && typeof d === 'string' && d.match(/[^\x00-\x7F]/);
12742
+ }
12743
+ },
12744
+ order: {
12745
+ asc: __diacriticSort,
12746
+ desc: function (a, b) {
12747
+ return __diacriticSort(a, b) * -1;
12748
+ }
12749
+ },
12750
+ search: _filterString(false, true)
12751
+ });
12752
+
12279
12753
 
12280
12754
  DataTable.type('html', {
12281
- detect: function ( d ) {
12282
- return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
12283
- 'html' : null;
12755
+ detect: {
12756
+ allOf: function ( d ) {
12757
+ return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
12758
+ },
12759
+ oneOf: function ( d ) {
12760
+ // At least one data point must contain a `<`
12761
+ return ! _empty( d ) && typeof d === 'string' && d.indexOf('<') !== -1;
12762
+ }
12284
12763
  },
12285
12764
  order: {
12286
12765
  pre: function ( a ) {
@@ -12295,18 +12774,48 @@
12295
12774
  });
12296
12775
 
12297
12776
 
12777
+ DataTable.type('html-utf8', {
12778
+ detect: {
12779
+ allOf: function ( d ) {
12780
+ return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
12781
+ },
12782
+ oneOf: function ( d ) {
12783
+ // At least one data point must contain a `<` and a non-ASCII character
12784
+ // eslint-disable-next-line compat/compat
12785
+ return navigator.languages &&
12786
+ ! _empty( d ) &&
12787
+ typeof d === 'string' &&
12788
+ d.indexOf('<') !== -1 &&
12789
+ typeof d === 'string' && d.match(/[^\x00-\x7F]/);
12790
+ }
12791
+ },
12792
+ order: {
12793
+ asc: __diacriticHtmlSort,
12794
+ desc: function (a, b) {
12795
+ return __diacriticHtmlSort(a, b) * -1;
12796
+ }
12797
+ },
12798
+ search: _filterString(true, true)
12799
+ });
12800
+
12801
+
12298
12802
  DataTable.type('date', {
12299
12803
  className: 'dt-type-date',
12300
- detect: function ( d )
12301
- {
12302
- // V8 tries _very_ hard to make a string passed into `Date.parse()`
12303
- // valid, so we need to use a regex to restrict date formats. Use a
12304
- // plug-in for anything other than ISO8601 style strings
12305
- if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
12306
- return null;
12804
+ detect: {
12805
+ allOf: function ( d ) {
12806
+ // V8 tries _very_ hard to make a string passed into `Date.parse()`
12807
+ // valid, so we need to use a regex to restrict date formats. Use a
12808
+ // plug-in for anything other than ISO8601 style strings
12809
+ if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
12810
+ return null;
12811
+ }
12812
+ var parsed = Date.parse(d);
12813
+ return (parsed !== null && !isNaN(parsed)) || _empty(d);
12814
+ },
12815
+ oneOf: function ( d ) {
12816
+ // At least one entry must be a date or a string with a date
12817
+ return (d instanceof Date) || (typeof d === 'string' && _re_date.test(d));
12307
12818
  }
12308
- var parsed = Date.parse(d);
12309
- return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
12310
12819
  },
12311
12820
  order: {
12312
12821
  pre: function ( d ) {
@@ -12319,10 +12828,16 @@
12319
12828
 
12320
12829
  DataTable.type('html-num-fmt', {
12321
12830
  className: 'dt-type-numeric',
12322
- detect: function ( d, settings )
12323
- {
12324
- var decimal = settings.oLanguage.sDecimal;
12325
- return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt' : null;
12831
+ detect: {
12832
+ allOf: function ( d, settings ) {
12833
+ var decimal = settings.oLanguage.sDecimal;
12834
+ return _htmlNumeric( d, decimal, true, false );
12835
+ },
12836
+ oneOf: function (d, settings) {
12837
+ // At least one data point must contain a numeric value
12838
+ var decimal = settings.oLanguage.sDecimal;
12839
+ return _htmlNumeric( d, decimal, true, false );
12840
+ }
12326
12841
  },
12327
12842
  order: {
12328
12843
  pre: function ( d, s ) {
@@ -12336,10 +12851,16 @@
12336
12851
 
12337
12852
  DataTable.type('html-num', {
12338
12853
  className: 'dt-type-numeric',
12339
- detect: function ( d, settings )
12340
- {
12341
- var decimal = settings.oLanguage.sDecimal;
12342
- return _htmlNumeric( d, decimal ) ? 'html-num' : null;
12854
+ detect: {
12855
+ allOf: function ( d, settings ) {
12856
+ var decimal = settings.oLanguage.sDecimal;
12857
+ return _htmlNumeric( d, decimal, false, true );
12858
+ },
12859
+ oneOf: function (d, settings) {
12860
+ // At least one data point must contain a numeric value
12861
+ var decimal = settings.oLanguage.sDecimal;
12862
+ return _htmlNumeric( d, decimal, false, false );
12863
+ }
12343
12864
  },
12344
12865
  order: {
12345
12866
  pre: function ( d, s ) {
@@ -12353,10 +12874,16 @@
12353
12874
 
12354
12875
  DataTable.type('num-fmt', {
12355
12876
  className: 'dt-type-numeric',
12356
- detect: function ( d, settings )
12357
- {
12358
- var decimal = settings.oLanguage.sDecimal;
12359
- return _isNumber( d, decimal, true ) ? 'num-fmt' : null;
12877
+ detect: {
12878
+ allOf: function ( d, settings ) {
12879
+ var decimal = settings.oLanguage.sDecimal;
12880
+ return _isNumber( d, decimal, true, true );
12881
+ },
12882
+ oneOf: function (d, settings) {
12883
+ // At least one data point must contain a numeric value
12884
+ var decimal = settings.oLanguage.sDecimal;
12885
+ return _isNumber( d, decimal, true, false );
12886
+ }
12360
12887
  },
12361
12888
  order: {
12362
12889
  pre: function ( d, s ) {
@@ -12369,10 +12896,16 @@
12369
12896
 
12370
12897
  DataTable.type('num', {
12371
12898
  className: 'dt-type-numeric',
12372
- detect: function ( d, settings )
12373
- {
12374
- var decimal = settings.oLanguage.sDecimal;
12375
- return _isNumber( d, decimal ) ? 'num' : null;
12899
+ detect: {
12900
+ allOf: function ( d, settings ) {
12901
+ var decimal = settings.oLanguage.sDecimal;
12902
+ return _isNumber( d, decimal, false, true );
12903
+ },
12904
+ oneOf: function (d, settings) {
12905
+ // At least one data point must contain a numeric value
12906
+ var decimal = settings.oLanguage.sDecimal;
12907
+ return _isNumber( d, decimal, false, false );
12908
+ }
12376
12909
  },
12377
12910
  order: {
12378
12911
  pre: function (d, s) {
@@ -12456,11 +12989,18 @@
12456
12989
  // `DT` namespace will allow the event to be removed automatically
12457
12990
  // on destroy, while the `dt` namespaced event is the one we are
12458
12991
  // listening for
12459
- $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting ) {
12992
+ $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) {
12460
12993
  if ( settings !== ctx ) { // need to check this this is the host
12461
12994
  return; // table, not a nested one
12462
12995
  }
12463
12996
 
12997
+ var sorting = ctx.sortDetails;
12998
+
12999
+ if (! sorting) {
13000
+ return;
13001
+ }
13002
+
13003
+ var i;
12464
13004
  var orderClasses = classes.order;
12465
13005
  var columns = ctx.api.columns( cell );
12466
13006
  var col = settings.aoColumns[columns.flatten()[0]];
@@ -12468,9 +13008,8 @@
12468
13008
  var ariaType = '';
12469
13009
  var indexes = columns.indexes();
12470
13010
  var sortDirs = columns.orderable(true).flatten();
12471
- var orderedColumns = ',' + sorting.map( function (val) {
12472
- return val.col;
12473
- } ).join(',') + ',';
13011
+ var orderedColumns = _pluck(sorting, 'col');
13012
+ var tabIndex = settings.iTabIndex;
12474
13013
 
12475
13014
  cell
12476
13015
  .removeClass(
@@ -12480,10 +13019,18 @@
12480
13019
  .toggleClass( orderClasses.none, ! orderable )
12481
13020
  .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
12482
13021
  .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
13022
+
13023
+ // Determine if all of the columns that this cell covers are included in the
13024
+ // current ordering
13025
+ var isOrdering = true;
12483
13026
 
12484
- var sortIdx = orderedColumns.indexOf( ',' + indexes.toArray().join(',') + ',' );
13027
+ for (i=0; i<indexes.length; i++) {
13028
+ if (! orderedColumns.includes(indexes[i])) {
13029
+ isOrdering = false;
13030
+ }
13031
+ }
12485
13032
 
12486
- if ( sortIdx !== -1 ) {
13033
+ if ( isOrdering ) {
12487
13034
  // Get the ordering direction for the columns under this cell
12488
13035
  // Note that it is possible for a cell to be asc and desc sorting
12489
13036
  // (column spanning cells)
@@ -12495,8 +13042,19 @@
12495
13042
  );
12496
13043
  }
12497
13044
 
12498
- // The ARIA spec says that only one column should be marked with aria-sort
12499
- if ( sortIdx === 0 ) {
13045
+ // Find the first visible column that has ordering applied to it - it get's
13046
+ // the aria information, as the ARIA spec says that only one column should
13047
+ // be marked with aria-sort
13048
+ var firstVis = -1; // column index
13049
+
13050
+ for (i=0; i<orderedColumns.length; i++) {
13051
+ if (settings.aoColumns[orderedColumns[i]].bVisible) {
13052
+ firstVis = orderedColumns[i];
13053
+ break;
13054
+ }
13055
+ }
13056
+
13057
+ if (indexes[0] == firstVis) {
12500
13058
  var firstSort = sorting[0];
12501
13059
  var sortOrder = col.asSorting;
12502
13060
 
@@ -12509,14 +13067,20 @@
12509
13067
  cell.removeAttr('aria-sort');
12510
13068
  }
12511
13069
 
12512
- cell.attr('aria-label', orderable
12513
- ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
12514
- : col.ariaTitle
12515
- );
12516
-
13070
+ // Make the headers tab-able for keyboard navigation
12517
13071
  if (orderable) {
12518
- cell.find('.dt-column-title').attr('role', 'button');
12519
- cell.attr('tabindex', 0)
13072
+ var orderSpan = cell.find('.dt-column-order');
13073
+
13074
+ orderSpan
13075
+ .attr('role', 'button')
13076
+ .attr('aria-label', orderable
13077
+ ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
13078
+ : col.ariaTitle
13079
+ );
13080
+
13081
+ if (tabIndex !== -1) {
13082
+ orderSpan.attr('tabindex', tabIndex);
13083
+ }
12520
13084
  }
12521
13085
  } );
12522
13086
  }
@@ -12524,27 +13088,68 @@
12524
13088
 
12525
13089
  layout: {
12526
13090
  _: function ( settings, container, items ) {
13091
+ var classes = settings.oClasses.layout;
12527
13092
  var row = $('<div/>')
12528
- .addClass('dt-layout-row')
13093
+ .attr('id', items.id || null)
13094
+ .addClass(items.className || classes.row)
12529
13095
  .appendTo( container );
12530
13096
 
12531
- $.each( items, function (key, val) {
12532
- var klass = ! val.table ?
12533
- 'dt-'+key+' ' :
12534
- '';
13097
+ DataTable.ext.renderer.layout._forLayoutRow(items, function (key, val) {
13098
+ if (key === 'id' || key === 'className') {
13099
+ return;
13100
+ }
13101
+
13102
+ var klass = '';
12535
13103
 
12536
13104
  if (val.table) {
12537
- row.addClass('dt-layout-table');
13105
+ row.addClass(classes.tableRow);
13106
+ klass += classes.tableCell + ' ';
13107
+ }
13108
+
13109
+ if (key === 'start') {
13110
+ klass += classes.start;
13111
+ }
13112
+ else if (key === 'end') {
13113
+ klass += classes.end;
13114
+ }
13115
+ else {
13116
+ klass += classes.full;
12538
13117
  }
12539
13118
 
12540
13119
  $('<div/>')
12541
13120
  .attr({
12542
13121
  id: val.id || null,
12543
- "class": 'dt-layout-cell '+klass+(val.className || '')
13122
+ "class": val.className
13123
+ ? val.className
13124
+ : classes.cell + ' ' + klass
12544
13125
  })
12545
13126
  .append( val.contents )
12546
13127
  .appendTo( row );
12547
- } );
13128
+ });
13129
+ },
13130
+
13131
+ // Shared for use by the styling frameworks
13132
+ _forLayoutRow: function (items, fn) {
13133
+ // As we are inserting dom elements, we need start / end in a
13134
+ // specific order, this function is used for sorting the layout
13135
+ // keys.
13136
+ var layoutEnum = function (x) {
13137
+ switch (x) {
13138
+ case '': return 0;
13139
+ case 'start': return 1;
13140
+ case 'end': return 2;
13141
+ default: return 3;
13142
+ }
13143
+ };
13144
+
13145
+ Object
13146
+ .keys(items)
13147
+ .sort(function (a, b) {
13148
+ return layoutEnum(a) - layoutEnum(b);
13149
+ })
13150
+ .forEach(function (key) {
13151
+ fn(key, items[key]);
13152
+ });
12548
13153
  }
12549
13154
  }
12550
13155
  } );
@@ -12564,6 +13169,25 @@
12564
13169
  }
12565
13170
  };
12566
13171
 
13172
+ function _divProp(el, prop, val) {
13173
+ if (val) {
13174
+ el[prop] = val;
13175
+ }
13176
+ }
13177
+
13178
+ DataTable.feature.register( 'div', function ( settings, opts ) {
13179
+ var n = $('<div>')[0];
13180
+
13181
+ if (opts) {
13182
+ _divProp(n, 'className', opts.className);
13183
+ _divProp(n, 'id', opts.id);
13184
+ _divProp(n, 'innerHTML', opts.html);
13185
+ _divProp(n, 'textContent', opts.text);
13186
+ }
13187
+
13188
+ return n;
13189
+ } );
13190
+
12567
13191
  DataTable.feature.register( 'info', function ( settings, opts ) {
12568
13192
  // For compatibility with the legacy `info` top level option
12569
13193
  if (! settings.oFeatures.bInfo) {
@@ -12663,6 +13287,7 @@
12663
13287
 
12664
13288
  opts = $.extend({
12665
13289
  placeholder: language.sSearchPlaceholder,
13290
+ processing: false,
12666
13291
  text: language.sSearch
12667
13292
  }, opts);
12668
13293
 
@@ -12706,13 +13331,15 @@
12706
13331
 
12707
13332
  /* Now do the filter */
12708
13333
  if ( val != previousSearch.search ) {
12709
- previousSearch.search = val;
12710
-
12711
- _fnFilterComplete( settings, previousSearch );
12712
-
12713
- // Need to redraw, without resorting
12714
- settings._iDisplayStart = 0;
12715
- _fnDraw( settings );
13334
+ _fnProcessingRun(settings, opts.processing, function () {
13335
+ previousSearch.search = val;
13336
+
13337
+ _fnFilterComplete( settings, previousSearch );
13338
+
13339
+ // Need to redraw, without resorting
13340
+ settings._iDisplayStart = 0;
13341
+ _fnDraw( settings );
13342
+ });
12716
13343
  }
12717
13344
  };
12718
13345
 
@@ -12770,17 +13397,21 @@
12770
13397
  opts = $.extend({
12771
13398
  buttons: DataTable.ext.pager.numbers_length,
12772
13399
  type: settings.sPaginationType,
12773
- boundaryNumbers: true
13400
+ boundaryNumbers: true,
13401
+ firstLast: true,
13402
+ previousNext: true,
13403
+ numbers: true
12774
13404
  }, opts);
12775
13405
 
12776
- // To be removed in 2.1
12777
- if (opts.numbers) {
12778
- opts.buttons = opts.numbers;
12779
- }
12780
-
12781
- var host = $('<div/>').addClass( settings.oClasses.paging.container + ' paging_' + opts.type );
13406
+ var host = $('<div/>')
13407
+ .addClass(settings.oClasses.paging.container + (opts.type ? ' paging_' + opts.type : ''))
13408
+ .append(
13409
+ $('<nav>')
13410
+ .attr('aria-label', 'pagination')
13411
+ .addClass(settings.oClasses.paging.nav)
13412
+ );
12782
13413
  var draw = function () {
12783
- _pagingDraw(settings, host, opts);
13414
+ _pagingDraw(settings, host.children(), opts);
12784
13415
  };
12785
13416
 
12786
13417
  settings.aoDrawCallback.push(draw);
@@ -12791,13 +13422,39 @@
12791
13422
  return host;
12792
13423
  }, 'p' );
12793
13424
 
13425
+ /**
13426
+ * Dynamically create the button type array based on the configuration options.
13427
+ * This will only happen if the paging type is not defined.
13428
+ */
13429
+ function _pagingDynamic(opts) {
13430
+ var out = [];
13431
+
13432
+ if (opts.numbers) {
13433
+ out.push('numbers');
13434
+ }
13435
+
13436
+ if (opts.previousNext) {
13437
+ out.unshift('previous');
13438
+ out.push('next');
13439
+ }
13440
+
13441
+ if (opts.firstLast) {
13442
+ out.unshift('first');
13443
+ out.push('last');
13444
+ }
13445
+
13446
+ return out;
13447
+ }
13448
+
12794
13449
  function _pagingDraw(settings, host, opts) {
12795
13450
  if (! settings._bInitComplete) {
12796
13451
  return;
12797
13452
  }
12798
13453
 
12799
13454
  var
12800
- plugin = DataTable.ext.pager[ opts.type ],
13455
+ plugin = opts.type
13456
+ ? DataTable.ext.pager[ opts.type ]
13457
+ : _pagingDynamic,
12801
13458
  aria = settings.oLanguage.oAria.paginate || {},
12802
13459
  start = settings._iDisplayStart,
12803
13460
  len = settings._iDisplayLength,
@@ -12805,15 +13462,17 @@
12805
13462
  all = len === -1,
12806
13463
  page = all ? 0 : Math.ceil( start / len ),
12807
13464
  pages = all ? 1 : Math.ceil( visRecords / len ),
12808
- buttons = plugin()
13465
+ buttons = [],
13466
+ buttonEls = [],
13467
+ buttonsNested = plugin(opts)
12809
13468
  .map(function (val) {
12810
13469
  return val === 'numbers'
12811
13470
  ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers)
12812
13471
  : val;
12813
- })
12814
- .flat();
13472
+ });
12815
13473
 
12816
- var buttonEls = [];
13474
+ // .flat() would be better, but not supported in old Safari
13475
+ buttons = buttons.concat.apply(buttons, buttonsNested);
12817
13476
 
12818
13477
  for (var i=0 ; i<buttons.length ; i++) {
12819
13478
  var button = buttons[i];
@@ -12827,14 +13486,24 @@
12827
13486
  btnInfo.disabled
12828
13487
  );
12829
13488
 
13489
+ var ariaLabel = typeof button === 'string'
13490
+ ? aria[ button ]
13491
+ : aria.number
13492
+ ? aria.number + (button+1)
13493
+ : null;
13494
+
12830
13495
  // Common attributes
12831
13496
  $(btn.clicker).attr({
12832
13497
  'aria-controls': settings.sTableId,
12833
13498
  'aria-disabled': btnInfo.disabled ? 'true' : null,
12834
13499
  'aria-current': btnInfo.active ? 'page' : null,
12835
- 'aria-label': aria[ button ],
13500
+ 'aria-label': ariaLabel,
12836
13501
  'data-dt-idx': button,
12837
- 'tabIndex': btnInfo.disabled ? -1 : settings.iTabIndex,
13502
+ 'tabIndex': btnInfo.disabled
13503
+ ? -1
13504
+ : settings.iTabIndex && btn.clicker[0].nodeName.toLowerCase() !== 'span'
13505
+ ? settings.iTabIndex
13506
+ : null, // `0` doesn't need a tabIndex since it is the default
12838
13507
  });
12839
13508
 
12840
13509
  if (typeof button !== 'number') {
@@ -12866,12 +13535,16 @@
12866
13535
 
12867
13536
  // Responsive - check if the buttons are over two lines based on the
12868
13537
  // height of the buttons and the container.
12869
- if (
12870
- buttonEls.length && // any buttons
12871
- opts.numbers > 1 && // prevent infinite
12872
- $(host).height() >= ($(buttonEls[0]).outerHeight() * 2) - 10
12873
- ) {
12874
- _pagingDraw(settings, host, $.extend({}, opts, { numbers: opts.numbers - 2 }));
13538
+ if (buttonEls.length) {
13539
+ var outerHeight = $(buttonEls[0]).outerHeight();
13540
+
13541
+ if (
13542
+ opts.buttons > 1 && // prevent infinite
13543
+ outerHeight > 0 && // will be 0 if hidden
13544
+ $(host).height() >= (outerHeight * 2) - 10
13545
+ ) {
13546
+ _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 }));
13547
+ }
12875
13548
  }
12876
13549
  }
12877
13550
 
@@ -12895,7 +13568,6 @@
12895
13568
  switch ( button ) {
12896
13569
  case 'ellipsis':
12897
13570
  o.display = '&#x2026;';
12898
- o.disabled = true;
12899
13571
  break;
12900
13572
 
12901
13573
  case 'first':
@@ -13073,22 +13745,23 @@
13073
13745
  }
13074
13746
 
13075
13747
  // Wrapper element - use a span as a holder for where the select will go
13748
+ var tmpId = 'tmp-' + (+new Date())
13076
13749
  var div = $('<div/>')
13077
13750
  .addClass( classes.container )
13078
13751
  .append(
13079
- str.replace( '_MENU_', '<span></span>' )
13752
+ str.replace( '_MENU_', '<span id="'+tmpId+'"></span>' )
13080
13753
  );
13081
13754
 
13082
13755
  // Save text node content for macro updating
13083
13756
  var textNodes = [];
13084
- div.find('label')[0].childNodes.forEach(function (el) {
13757
+ Array.prototype.slice.call(div.find('label')[0].childNodes).forEach(function (el) {
13085
13758
  if (el.nodeType === Node.TEXT_NODE) {
13086
13759
  textNodes.push({
13087
13760
  el: el,
13088
13761
  text: el.textContent
13089
13762
  });
13090
13763
  }
13091
- })
13764
+ });
13092
13765
 
13093
13766
  // Update the label text in case it has an entries value
13094
13767
  var updateEntries = function (len) {
@@ -13099,7 +13772,6 @@
13099
13772
 
13100
13773
  // Next, the select itself, along with the options
13101
13774
  var select = $('<select/>', {
13102
- 'name': tableId+'_length',
13103
13775
  'aria-controls': tableId,
13104
13776
  'class': classes.select
13105
13777
  } );
@@ -13119,7 +13791,7 @@
13119
13791
  __lengthCounter++;
13120
13792
 
13121
13793
  // Swap in the select list
13122
- div.find('span').replaceWith(select);
13794
+ div.find('#' + tmpId).replaceWith(select);
13123
13795
 
13124
13796
  // Can't use `select` variable as user might provide their own and the
13125
13797
  // reference is broken by the use of outerHTML