oxidized-web 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.

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