@ntlab/ntjs-assets 2.0.31 → 2.0.33

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.
@@ -1,11 +1,11 @@
1
- /*! DataTables 2.0.8
1
+ /*! DataTables 2.1.2
2
2
  * © SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
5
5
  /**
6
6
  * @summary DataTables
7
7
  * @description Paginate, search and order HTML tables
8
- * @version 2.0.8
8
+ * @version 2.1.2
9
9
  * @author SpryMedia Ltd
10
10
  * @contact www.datatables.net
11
11
  * @copyright SpryMedia Ltd.
@@ -104,7 +104,6 @@
104
104
 
105
105
  var i=0, iLen;
106
106
  var sId = this.getAttribute( 'id' );
107
- var bInitHandedOff = false;
108
107
  var defaults = DataTable.defaults;
109
108
  var $this = $(this);
110
109
 
@@ -254,6 +253,7 @@
254
253
  "rowId",
255
254
  "caption",
256
255
  "layout",
256
+ "orderDescReverse",
257
257
  [ "iCookieDuration", "iStateDuration" ], // backwards compat
258
258
  [ "oSearch", "oPreviousSearch" ],
259
259
  [ "aoSearchCols", "aoPreSearchCols" ],
@@ -300,38 +300,14 @@
300
300
  oSettings._iDisplayStart = oInit.iDisplayStart;
301
301
  }
302
302
 
303
- /* Language definitions */
304
- var oLanguage = oSettings.oLanguage;
305
- $.extend( true, oLanguage, oInit.oLanguage );
306
-
307
- if ( oLanguage.sUrl )
303
+ var defer = oInit.iDeferLoading;
304
+ if ( defer !== null )
308
305
  {
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 );
306
+ oSettings.deferLoading = true;
319
307
 
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]);
308
+ var tmp = Array.isArray(defer);
309
+ oSettings._iRecordsDisplay = tmp ? defer[0] : defer;
310
+ oSettings._iRecordsTotal = tmp ? defer[1] : defer;
335
311
  }
336
312
 
337
313
  /*
@@ -398,113 +374,112 @@
398
374
  } );
399
375
  }
400
376
 
377
+ // Must be done after everything which can be overridden by the state saving!
378
+ _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
379
+
401
380
  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
- */
381
+ if ( oInit.bStateSave )
382
+ {
383
+ features.bStateSave = true;
384
+ }
407
385
 
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
- }
386
+ // If aaSorting is not defined, then we use the first indicator in asSorting
387
+ // in case that has been altered, so the default sort reflects that option
388
+ if ( oInit.aaSorting === undefined ) {
389
+ var sorting = oSettings.aaSorting;
390
+ for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
391
+ sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
415
392
  }
393
+ }
416
394
 
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
- } );
395
+ // Do a first pass on the sorting classes (allows any size changes to be taken into
396
+ // account, and also will apply sorting disabled classes if disabled
397
+ _fnSortingClasses( oSettings );
427
398
 
399
+ _fnCallbackReg( oSettings, 'aoDrawCallback', function () {
400
+ if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
401
+ _fnSortingClasses( oSettings );
402
+ }
403
+ } );
428
404
 
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
405
 
435
- if ( oSettings.caption ) {
436
- if ( caption.length === 0 ) {
437
- caption = $('<caption/>').appendTo( $this );
438
- }
406
+ /*
407
+ * Table HTML init
408
+ * Cache the header, body and footer as required, creating them if needed
409
+ */
410
+ var caption = $this.children('caption');
439
411
 
440
- caption.html( oSettings.caption );
412
+ if ( oSettings.caption ) {
413
+ if ( caption.length === 0 ) {
414
+ caption = $('<caption/>').appendTo( $this );
441
415
  }
442
416
 
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
- }
417
+ caption.html( oSettings.caption );
418
+ }
449
419
 
450
- if ( thead.length === 0 ) {
451
- thead = $('<thead/>').appendTo($this);
452
- }
453
- oSettings.nTHead = thead[0];
454
- $('tr', thead).addClass(oClasses.thead.row);
420
+ // Store the caption side, so we can remove the element from the document
421
+ // when creating the element
422
+ if (caption.length) {
423
+ caption[0]._captionSide = caption.css('caption-side');
424
+ oSettings.captionNode = caption[0];
425
+ }
455
426
 
456
- var tbody = $this.children('tbody');
457
- if ( tbody.length === 0 ) {
458
- tbody = $('<tbody/>').insertAfter(thead);
459
- }
460
- oSettings.nTBody = tbody[0];
427
+ if ( thead.length === 0 ) {
428
+ thead = $('<thead/>').appendTo($this);
429
+ }
430
+ oSettings.nTHead = thead[0];
431
+ $('tr', thead).addClass(oClasses.thead.row);
461
432
 
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);
433
+ var tbody = $this.children('tbody');
434
+ if ( tbody.length === 0 ) {
435
+ tbody = $('<tbody/>').insertAfter(thead);
436
+ }
437
+ oSettings.nTBody = tbody[0];
470
438
 
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
- }
439
+ var tfoot = $this.children('tfoot');
440
+ if ( tfoot.length === 0 ) {
441
+ // If we are a scrolling table, and no footer has been given, then we need to create
442
+ // a tfoot element for the caption element to be appended to
443
+ tfoot = $('<tfoot/>').appendTo($this);
444
+ }
445
+ oSettings.nTFoot = tfoot[0];
446
+ $('tr', tfoot).addClass(oClasses.tfoot.row);
481
447
 
482
- /* Copy the data index array */
483
- oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
448
+ // Copy the data index array
449
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
484
450
 
485
- /* Initialisation complete - table can be drawn */
486
- oSettings.bInitialised = true;
451
+ // Initialisation complete - table can be drawn
452
+ oSettings.bInitialised = true;
487
453
 
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
- };
454
+ // Language definitions
455
+ var oLanguage = oSettings.oLanguage;
456
+ $.extend( true, oLanguage, oInit.oLanguage );
495
457
 
496
- /* Must be done after everything which can be overridden by the state saving! */
497
- _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState );
458
+ if ( oLanguage.sUrl ) {
459
+ // Get the language definitions from a file
460
+ $.ajax( {
461
+ dataType: 'json',
462
+ url: oLanguage.sUrl,
463
+ success: function ( json ) {
464
+ _fnCamelToHungarian( defaults.oLanguage, json );
465
+ $.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
498
466
 
499
- if ( oInit.bStateSave )
500
- {
501
- features.bStateSave = true;
502
- _fnLoadState( oSettings, oInit, loadedInit );
467
+ _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
468
+ _fnInitialise( oSettings );
469
+ },
470
+ error: function () {
471
+ // Error occurred loading language file
472
+ _fnLog( oSettings, 0, 'i18n file loading error', 21 );
473
+
474
+ // Continue on as best we can
475
+ _fnInitialise( oSettings );
476
+ }
477
+ } );
503
478
  }
504
479
  else {
505
- loadedInit();
480
+ _fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
481
+ _fnInitialise( oSettings );
506
482
  }
507
-
508
483
  } );
509
484
  _that = null;
510
485
  return this;
@@ -1021,6 +996,15 @@
1021
996
  info: {
1022
997
  container: 'dt-info'
1023
998
  },
999
+ layout: {
1000
+ row: 'dt-layout-row',
1001
+ cell: 'dt-layout-cell',
1002
+ tableRow: 'dt-layout-table',
1003
+ tableCell: '',
1004
+ start: 'dt-layout-start',
1005
+ end: 'dt-layout-end',
1006
+ full: 'dt-layout-full'
1007
+ },
1024
1008
  length: {
1025
1009
  container: 'dt-length',
1026
1010
  select: 'dt-input'
@@ -1144,7 +1128,7 @@
1144
1128
  };
1145
1129
 
1146
1130
 
1147
- var _isNumber = function ( d, decimalPoint, formatted ) {
1131
+ var _isNumber = function ( d, decimalPoint, formatted, allowEmpty ) {
1148
1132
  var type = typeof d;
1149
1133
  var strType = type === 'string';
1150
1134
 
@@ -1155,7 +1139,7 @@
1155
1139
  // If empty return immediately so there must be a number if it is a
1156
1140
  // formatted string (this stops the string "k", or "kr", etc being detected
1157
1141
  // as a formatted number for currency
1158
- if ( _empty( d ) ) {
1142
+ if ( allowEmpty && _empty( d ) ) {
1159
1143
  return true;
1160
1144
  }
1161
1145
 
@@ -1177,8 +1161,8 @@
1177
1161
  };
1178
1162
 
1179
1163
  // Is a string a number surrounded by HTML?
1180
- var _htmlNumeric = function ( d, decimalPoint, formatted ) {
1181
- if ( _empty( d ) ) {
1164
+ var _htmlNumeric = function ( d, decimalPoint, formatted, allowEmpty ) {
1165
+ if ( allowEmpty && _empty( d ) ) {
1182
1166
  return true;
1183
1167
  }
1184
1168
 
@@ -1190,7 +1174,7 @@
1190
1174
  var html = _isHtml( d );
1191
1175
  return ! html ?
1192
1176
  null :
1193
- _isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
1177
+ _isNumber( _stripHtml( d ), decimalPoint, formatted, allowEmpty ) ?
1194
1178
  true :
1195
1179
  null;
1196
1180
  };
@@ -1286,6 +1270,10 @@
1286
1270
 
1287
1271
  // Replaceable function in api.util
1288
1272
  var _stripHtml = function (input) {
1273
+ if (! input) {
1274
+ return input;
1275
+ }
1276
+
1289
1277
  // Irrelevant check to workaround CodeQL's false positive on the regex
1290
1278
  if (input.length > _max_str_len) {
1291
1279
  throw new Error('Exceeded max str len');
@@ -2252,6 +2240,21 @@
2252
2240
  return a;
2253
2241
  }
2254
2242
 
2243
+ /**
2244
+ * Allow the result from a type detection function to be `true` while
2245
+ * translating that into a string. Old type detection functions will
2246
+ * return the type name if it passes. An obect store would be better,
2247
+ * but not backwards compatible.
2248
+ *
2249
+ * @param {*} typeDetect Object or function for type detection
2250
+ * @param {*} res Result from the type detection function
2251
+ * @returns Type name or false
2252
+ */
2253
+ function _typeResult (typeDetect, res) {
2254
+ return res === true
2255
+ ? typeDetect.name
2256
+ : res;
2257
+ }
2255
2258
 
2256
2259
  /**
2257
2260
  * Calculate the 'type' of a column
@@ -2266,7 +2269,13 @@
2266
2269
  var i, ien, j, jen, k, ken;
2267
2270
  var col, detectedType, cache;
2268
2271
 
2269
- // For each column, spin over the
2272
+ // If SSP then we don't have the full data set, so any type detection would be
2273
+ // unreliable and error prone
2274
+ if (_fnDataSource( settings ) === 'ssp') {
2275
+ return;
2276
+ }
2277
+
2278
+ // For each column, spin over the data type detection functions, seeing if one matches
2270
2279
  for ( i=0, ien=columns.length ; i<ien ; i++ ) {
2271
2280
  col = columns[i];
2272
2281
  cache = [];
@@ -2276,8 +2285,27 @@
2276
2285
  }
2277
2286
  else if ( ! col.sType ) {
2278
2287
  for ( j=0, jen=types.length ; j<jen ; j++ ) {
2279
- for ( k=0, ken=data.length ; k<ken ; k++ ) {
2288
+ var typeDetect = types[j];
2289
+
2290
+ // There can be either one, or three type detection functions
2291
+ var oneOf = typeDetect.oneOf;
2292
+ var allOf = typeDetect.allOf || typeDetect;
2293
+ var init = typeDetect.init;
2294
+ var one = false;
2295
+
2296
+ detectedType = null;
2297
+
2298
+ // Fast detect based on column assignment
2299
+ if (init) {
2300
+ detectedType = _typeResult(typeDetect, init(settings, col, i));
2301
+
2302
+ if (detectedType) {
2303
+ col.sType = detectedType;
2304
+ break;
2305
+ }
2306
+ }
2280
2307
 
2308
+ for ( k=0, ken=data.length ; k<ken ; k++ ) {
2281
2309
  if (! data[k]) {
2282
2310
  continue;
2283
2311
  }
@@ -2288,14 +2316,20 @@
2288
2316
  cache[k] = _fnGetCellData( settings, k, i, 'type' );
2289
2317
  }
2290
2318
 
2291
- detectedType = types[j]( cache[k], settings );
2319
+ // Only one data point in the column needs to match this function
2320
+ if (oneOf && ! one) {
2321
+ one = _typeResult(typeDetect, oneOf( cache[k], settings ));
2322
+ }
2323
+
2324
+ // All data points need to match this function
2325
+ detectedType = _typeResult(typeDetect, allOf( cache[k], settings ));
2292
2326
 
2293
2327
  // If null, then this type can't apply to this column, so
2294
2328
  // rather than testing all cells, break out. There is an
2295
2329
  // exception for the last type which is `html`. We need to
2296
2330
  // scan all rows since it is possible to mix string and HTML
2297
2331
  // types
2298
- if ( ! detectedType && j !== types.length-2 ) {
2332
+ if ( ! detectedType && j !== types.length-3 ) {
2299
2333
  break;
2300
2334
  }
2301
2335
 
@@ -2309,7 +2343,7 @@
2309
2343
 
2310
2344
  // Type is valid for all data points in the column - use this
2311
2345
  // type
2312
- if ( detectedType ) {
2346
+ if ( (oneOf && one && detectedType) || (!oneOf && detectedType) ) {
2313
2347
  col.sType = detectedType;
2314
2348
  break;
2315
2349
  }
@@ -2331,7 +2365,7 @@
2331
2365
 
2332
2366
  var renderer = _ext.type.render[col.sType];
2333
2367
 
2334
- // This can only happen once! There is no way to remover
2368
+ // This can only happen once! There is no way to remove
2335
2369
  // a renderer. After the first time the renderer has
2336
2370
  // already been set so createTr will run the renderer itself.
2337
2371
  if (renderer && ! col._render) {
@@ -3375,7 +3409,13 @@
3375
3409
  oSettings.bDrawing = true;
3376
3410
 
3377
3411
  /* Server-side processing draw intercept */
3378
- if ( !bServerSide )
3412
+ if ( oSettings.deferLoading )
3413
+ {
3414
+ oSettings.deferLoading = false;
3415
+ oSettings.iDraw++;
3416
+ _fnProcessingDisplay( oSettings, false );
3417
+ }
3418
+ else if ( !bServerSide )
3379
3419
  {
3380
3420
  oSettings.iDraw++;
3381
3421
  }
@@ -3476,6 +3516,9 @@
3476
3516
  filter = features.bFilter;
3477
3517
 
3478
3518
  if (recompute === undefined || recompute === true) {
3519
+ // Resolve any column types that are unknown due to addition or invalidation
3520
+ _fnColumnTypes( settings );
3521
+
3479
3522
  if ( sort ) {
3480
3523
  _fnSort( settings );
3481
3524
  }
@@ -3531,113 +3574,154 @@
3531
3574
 
3532
3575
 
3533
3576
  /**
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.
3577
+ * Expand the layout items into an object for the rendering function
3542
3578
  */
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;
3579
+ function _layoutItems (row, align, items) {
3580
+ if ( Array.isArray(items)) {
3581
+ for (var i=0 ; i<items.length ; i++) {
3582
+ _layoutItems(row, align, items[i]);
3551
3583
  }
3552
3584
 
3553
- var splitPos = pos.replace(/([A-Z])/g, ' $1').split(' ');
3585
+ return;
3586
+ }
3554
3587
 
3555
- if ( ! groups[ splitPos[0] ] ) {
3556
- groups[ splitPos[0] ] = {};
3557
- }
3588
+ var rowCell = row[align];
3558
3589
 
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
- });
3590
+ // If it is an object, then there can be multiple features contained in it
3591
+ if ( $.isPlainObject( items ) ) {
3592
+ // A feature plugin cannot be named "features" due to this check
3593
+ if (items.features) {
3594
+ if (items.rowId) {
3595
+ row.id = items.rowId;
3572
3596
  }
3573
- else {
3574
- contents.push(innerVal);
3597
+ if (items.rowClass) {
3598
+ row.className = items.rowClass;
3575
3599
  }
3576
- }
3577
3600
 
3578
- // Transform to an object with a contents property
3579
- if (! group[align] || ! group[align].contents) {
3580
- group[align] = { contents: [] };
3601
+ rowCell.id = items.id;
3602
+ rowCell.className = items.className;
3603
+
3604
+ _layoutItems(row, align, items.features);
3605
+ }
3606
+ else {
3607
+ Object.keys(items).map(function (key) {
3608
+ rowCell.contents.push( {
3609
+ feature: key,
3610
+ opts: items[key]
3611
+ });
3612
+ });
3581
3613
  }
3614
+ }
3615
+ else {
3616
+ rowCell.contents.push(items);
3617
+ }
3618
+ }
3619
+
3620
+ /**
3621
+ * Find, or create a layout row
3622
+ */
3623
+ function _layoutGetRow(rows, rowNum, align) {
3624
+ var row;
3625
+
3626
+ // Find existing rows
3627
+ for (var i=0; i<rows.length; i++) {
3628
+ row = rows[i];
3629
+
3630
+ if (row.rowNum === rowNum) {
3631
+ // full is on its own, but start and end share a row
3632
+ if (
3633
+ (align === 'full' && row.full) ||
3634
+ ((align === 'start' || align === 'end') && (row.start || row.end))
3635
+ ) {
3636
+ if (! row[align]) {
3637
+ row[align] = {
3638
+ contents: []
3639
+ };
3640
+ }
3582
3641
 
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]);
3642
+ return row;
3587
3643
  }
3588
3644
  }
3589
- else {
3590
- groupRun(group[ align ].contents, val);
3645
+ }
3646
+
3647
+ // If we get this far, then there was no match, create a new row
3648
+ row = {
3649
+ rowNum: rowNum
3650
+ };
3651
+
3652
+ row[align] = {
3653
+ contents: []
3654
+ };
3655
+
3656
+ rows.push(row);
3657
+
3658
+ return row;
3659
+ }
3660
+
3661
+ /**
3662
+ * Convert a `layout` object given by a user to the object structure needed
3663
+ * for the renderer. This is done twice, once for above and once for below
3664
+ * the table. Ordering must also be considered.
3665
+ *
3666
+ * @param {*} settings DataTables settings object
3667
+ * @param {*} layout Layout object to convert
3668
+ * @param {string} side `top` or `bottom`
3669
+ * @returns Converted array structure - one item for each row.
3670
+ */
3671
+ function _layoutArray ( settings, layout, side ) {
3672
+ var rows = [];
3673
+
3674
+ // Split out into an array
3675
+ $.each( layout, function ( pos, items ) {
3676
+ if (items === null) {
3677
+ return;
3591
3678
  }
3592
3679
 
3593
- // And make contents an array
3594
- if ( ! Array.isArray( group[ align ].contents ) ) {
3595
- group[ align ].contents = [ group[ align ].contents ];
3680
+ var parts = pos.match(/^([a-z]+)([0-9]*)([A-Za-z]*)$/);
3681
+ var rowNum = parts[2]
3682
+ ? parts[2] * 1
3683
+ : 0;
3684
+ var align = parts[3]
3685
+ ? parts[3].toLowerCase()
3686
+ : 'full';
3687
+
3688
+ // Filter out the side we aren't interested in
3689
+ if (parts[1] !== side) {
3690
+ return;
3596
3691
  }
3597
- } );
3598
3692
 
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
- }
3693
+ // Get or create the row we should attach to
3694
+ var row = _layoutGetRow(rows, rowNum, align);
3605
3695
 
3606
- return {
3607
- name: pos,
3608
- val: groups[pos]
3609
- };
3610
- } )
3611
- .filter( function (item) {
3612
- return item !== null;
3613
- });
3696
+ _layoutItems(row, align, items);
3697
+ });
3614
3698
 
3615
3699
  // 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;
3700
+ rows.sort( function ( a, b ) {
3701
+ var order1 = a.rowNum;
3702
+ var order2 = b.rowNum;
3703
+
3704
+ // If both in the same row, then the row with `full` comes first
3705
+ if (order1 === order2) {
3706
+ var ret = a.full && ! b.full ? -1 : 1;
3707
+
3708
+ return side === 'bottom'
3709
+ ? ret * -1
3710
+ : ret;
3711
+ }
3619
3712
 
3620
3713
  return order2 - order1;
3621
3714
  } );
3622
-
3715
+
3716
+ // Invert for below the table
3623
3717
  if ( side === 'bottom' ) {
3624
- filtered.reverse();
3718
+ rows.reverse();
3625
3719
  }
3626
3720
 
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
- }
3721
+ for (var row = 0; row<rows.length; row++) {
3722
+ delete rows[row].rowNum;
3636
3723
 
3637
- if ( Object.keys(filtered[i].val).length ) {
3638
- rows.push( filtered[i].val );
3639
- _layoutResolve( settings, rows[ rows.length - 1 ] );
3640
- }
3724
+ _layoutResolve(settings, rows[row]);
3641
3725
  }
3642
3726
 
3643
3727
  return rows;
@@ -3661,6 +3745,10 @@
3661
3745
  };
3662
3746
 
3663
3747
  var resolve = function ( item ) {
3748
+ if (! row[ item ]) {
3749
+ return;
3750
+ }
3751
+
3664
3752
  var line = row[ item ].contents;
3665
3753
 
3666
3754
  for ( var i=0, ien=line.length ; i<ien ; i++ ) {
@@ -3688,9 +3776,9 @@
3688
3776
  }
3689
3777
  };
3690
3778
 
3691
- $.each( row, function ( key ) {
3692
- resolve( key );
3693
- } );
3779
+ resolve('start');
3780
+ resolve('end');
3781
+ resolve('full');
3694
3782
  }
3695
3783
 
3696
3784
 
@@ -4023,6 +4111,17 @@
4023
4111
  _fnLog( oSettings, 0, error );
4024
4112
  }
4025
4113
 
4114
+ // Microsoft often wrap JSON as a string in another JSON object
4115
+ // Let's handle that automatically
4116
+ if (json.d && typeof json.d === 'string') {
4117
+ try {
4118
+ json = JSON.parse(json.d);
4119
+ }
4120
+ catch (e) {
4121
+ // noop
4122
+ }
4123
+ }
4124
+
4026
4125
  oSettings.json = json;
4027
4126
 
4028
4127
  _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR], true );
@@ -4099,11 +4198,11 @@
4099
4198
  else {
4100
4199
  // Object to extend the base settings
4101
4200
  oSettings.jqXHR = $.ajax( baseAjax );
4201
+ }
4102
4202
 
4103
- // Restore for next time around
4104
- if ( ajaxData ) {
4105
- ajax.data = ajaxData;
4106
- }
4203
+ // Restore for next time around
4204
+ if ( ajaxData ) {
4205
+ ajax.data = ajaxData;
4107
4206
  }
4108
4207
  }
4109
4208
 
@@ -4328,10 +4427,6 @@
4328
4427
  {
4329
4428
  var columnsSearch = settings.aoPreSearchCols;
4330
4429
 
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
4430
  // In server-side processing all filtering is done by the server, so no point hanging around here
4336
4431
  if ( _fnDataSource( settings ) != 'ssp' )
4337
4432
  {
@@ -4637,65 +4732,86 @@
4637
4732
  function _fnInitialise ( settings )
4638
4733
  {
4639
4734
  var i, iAjaxStart=settings.iInitDisplayStart;
4735
+ var init = settings.oInit;
4736
+ var deferLoading = settings.deferLoading;
4737
+ var dataSrc = _fnDataSource( settings );
4640
4738
 
4641
- /* Ensure that the table data is fully initialised */
4739
+ // Ensure that the table data is fully initialised
4642
4740
  if ( ! settings.bInitialised ) {
4643
4741
  setTimeout( function(){ _fnInitialise( settings ); }, 200 );
4644
4742
  return;
4645
4743
  }
4646
4744
 
4647
- /* Build and draw the header / footer for the table */
4745
+ // Build the header / footer for the table
4648
4746
  _fnBuildHead( settings, 'header' );
4649
4747
  _fnBuildHead( settings, 'footer' );
4650
- _fnDrawHead( settings, settings.aoHeader );
4651
- _fnDrawHead( settings, settings.aoFooter );
4652
4748
 
4653
- // Enable features
4654
- _fnAddOptionsHtml( settings );
4655
- _fnSortInit( settings );
4749
+ // Load the table's state (if needed) and then render around it and draw
4750
+ _fnLoadState( settings, init, function () {
4751
+ // Then draw the header / footer
4752
+ _fnDrawHead( settings, settings.aoHeader );
4753
+ _fnDrawHead( settings, settings.aoFooter );
4656
4754
 
4657
- _colGroup( settings );
4755
+ // Local data load
4756
+ // Check if there is data passing into the constructor
4757
+ if ( init.aaData ) {
4758
+ for ( i=0 ; i<init.aaData.length ; i++ ) {
4759
+ _fnAddData( settings, init.aaData[ i ] );
4760
+ }
4761
+ }
4762
+ else if ( deferLoading || dataSrc == 'dom' ) {
4763
+ // Grab the data from the page
4764
+ _fnAddTr( settings, $(settings.nTBody).children('tr') );
4765
+ }
4658
4766
 
4659
- /* Okay to show that something is going on now */
4660
- _fnProcessingDisplay( settings, true );
4767
+ // Filter not yet applied - copy the display master
4768
+ settings.aiDisplay = settings.aiDisplayMaster.slice();
4661
4769
 
4662
- _fnCallbackFire( settings, null, 'preInit', [settings], true );
4770
+ // Enable features
4771
+ _fnAddOptionsHtml( settings );
4772
+ _fnSortInit( settings );
4663
4773
 
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 );
4774
+ _colGroup( settings );
4669
4775
 
4670
- var dataSrc = _fnDataSource( settings );
4776
+ /* Okay to show that something is going on now */
4777
+ _fnProcessingDisplay( settings, true );
4671
4778
 
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 );
4779
+ _fnCallbackFire( settings, null, 'preInit', [settings], true );
4678
4780
 
4679
- // Got the data - add it to the table
4680
- for ( i=0 ; i<aData.length ; i++ ) {
4681
- _fnAddData( settings, aData[i] );
4682
- }
4781
+ // If there is default sorting required - let's do it. The sort function
4782
+ // will do the drawing for us. Otherwise we draw the table regardless of the
4783
+ // Ajax source - this allows the table to look initialised for Ajax sourcing
4784
+ // data (show 'loading' message possibly)
4785
+ _fnReDraw( settings );
4683
4786
 
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;
4787
+ // Server-side processing init complete is done by _fnAjaxUpdateDraw
4788
+ if ( dataSrc != 'ssp' || deferLoading ) {
4789
+ // if there is an ajax source load the data
4790
+ if ( dataSrc == 'ajax' ) {
4791
+ _fnBuildAjax( settings, {}, function(json) {
4792
+ var aData = _fnAjaxDataSrc( settings, json );
4688
4793
 
4689
- _fnReDraw( settings );
4690
- _fnProcessingDisplay( settings, false );
4794
+ // Got the data - add it to the table
4795
+ for ( i=0 ; i<aData.length ; i++ ) {
4796
+ _fnAddData( settings, aData[i] );
4797
+ }
4798
+
4799
+ // Reset the init display for cookie saving. We've already done
4800
+ // a filter, and therefore cleared it before. So we need to make
4801
+ // it appear 'fresh'
4802
+ settings.iInitDisplayStart = iAjaxStart;
4803
+
4804
+ _fnReDraw( settings );
4805
+ _fnProcessingDisplay( settings, false );
4806
+ _fnInitComplete( settings );
4807
+ }, settings );
4808
+ }
4809
+ else {
4691
4810
  _fnInitComplete( settings );
4692
- }, settings );
4693
- }
4694
- else {
4695
- _fnInitComplete( settings );
4696
- _fnProcessingDisplay( settings, false );
4811
+ _fnProcessingDisplay( settings, false );
4812
+ }
4697
4813
  }
4698
- }
4814
+ } );
4699
4815
  }
4700
4816
 
4701
4817
 
@@ -4852,6 +4968,30 @@
4852
4968
  {
4853
4969
  _fnCallbackFire( settings, null, 'processing', [settings, show] );
4854
4970
  }
4971
+
4972
+ /**
4973
+ * Show the processing element if an action takes longer than a given time
4974
+ *
4975
+ * @param {*} settings DataTables settings object
4976
+ * @param {*} enable Do (true) or not (false) async processing (local feature enablement)
4977
+ * @param {*} run Function to run
4978
+ */
4979
+ function _fnProcessingRun( settings, enable, run ) {
4980
+ if (! enable) {
4981
+ // Immediate execution, synchronous
4982
+ run();
4983
+ }
4984
+ else {
4985
+ _fnProcessingDisplay(settings, true);
4986
+
4987
+ // Allow the processing display to show if needed
4988
+ setTimeout(function () {
4989
+ run();
4990
+
4991
+ _fnProcessingDisplay(settings, false);
4992
+ }, 0);
4993
+ }
4994
+ }
4855
4995
  /**
4856
4996
  * Add any control elements for the table - specifically scrolling
4857
4997
  * @param {object} settings dataTables settings object
@@ -5182,8 +5322,17 @@
5182
5322
  tableWidthAttr = table.getAttribute('width'), // from DOM element
5183
5323
  tableContainer = table.parentNode,
5184
5324
  i, column, columnIdx;
5185
-
5325
+
5186
5326
  var styleWidth = table.style.width;
5327
+
5328
+ // If there is no width applied as a CSS style or as an attribute, we assume that
5329
+ // the width is intended to be 100%, which is usually is in CSS, but it is very
5330
+ // difficult to correctly parse the rules to get the final result.
5331
+ if ( ! styleWidth && ! tableWidthAttr) {
5332
+ table.style.width = '100%';
5333
+ styleWidth = '100%';
5334
+ }
5335
+
5187
5336
  if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
5188
5337
  tableWidthAttr = styleWidth;
5189
5338
  }
@@ -5497,22 +5646,16 @@
5497
5646
  }
5498
5647
 
5499
5648
  if (run) {
5500
- _fnProcessingDisplay( settings, true );
5501
-
5502
- // Allow the processing display to show
5503
- setTimeout( function () {
5649
+ _fnProcessingRun(settings, true, function () {
5504
5650
  _fnSort( settings );
5505
5651
  _fnSortDisplay( settings, settings.aiDisplay );
5506
5652
 
5507
- // Sort processing done - redraw has its own processing display
5508
- _fnProcessingDisplay( settings, false );
5509
-
5510
5653
  _fnReDraw( settings, false, false );
5511
5654
 
5512
5655
  if (callback) {
5513
5656
  callback();
5514
5657
  }
5515
- }, 0);
5658
+ });
5516
5659
  }
5517
5660
  }
5518
5661
  } );
@@ -5671,11 +5814,6 @@
5671
5814
  displayMaster = oSettings.aiDisplayMaster,
5672
5815
  aSort;
5673
5816
 
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 );
5678
-
5679
5817
  // Allow a specific column to be sorted, which will _not_ alter the display
5680
5818
  // master
5681
5819
  if (col !== undefined) {
@@ -5712,7 +5850,7 @@
5712
5850
 
5713
5851
  // If the first sort is desc, then reverse the array to preserve original
5714
5852
  // order, just in reverse
5715
- if (aSort.length && aSort[0].dir === 'desc') {
5853
+ if (aSort.length && aSort[0].dir === 'desc' && oSettings.orderDescReverse) {
5716
5854
  aiOrig.reverse();
5717
5855
  }
5718
5856
 
@@ -5782,6 +5920,7 @@
5782
5920
  if (col === undefined) {
5783
5921
  // Tell the draw function that we have sorted the data
5784
5922
  oSettings.bSorted = true;
5923
+ oSettings.sortDetails = aSort;
5785
5924
 
5786
5925
  _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort] );
5787
5926
  }
@@ -9203,23 +9342,60 @@
9203
9342
  } );
9204
9343
 
9205
9344
  /**
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) {
9345
+ * Set the libraries that DataTables uses, or the global objects.
9346
+ * Note that the arguments can be either way around (legacy support)
9347
+ * and the second is optional. See docs.
9348
+ */
9349
+ DataTable.use = function (arg1, arg2) {
9350
+ // Reverse arguments for legacy support
9351
+ var module = typeof arg1 === 'string'
9352
+ ? arg2
9353
+ : arg1;
9354
+ var type = typeof arg2 === 'string'
9355
+ ? arg2
9356
+ : arg1;
9357
+
9358
+ // Getter
9359
+ if (module === undefined && typeof type === 'string') {
9360
+ switch (type) {
9361
+ case 'lib':
9362
+ case 'jq':
9363
+ return $;
9364
+
9365
+ case 'win':
9366
+ return window;
9367
+
9368
+ case 'datetime':
9369
+ return DataTable.DateTime;
9370
+
9371
+ case 'luxon':
9372
+ return __luxon;
9373
+
9374
+ case 'moment':
9375
+ return __moment;
9376
+
9377
+ default:
9378
+ return null;
9379
+ }
9380
+ }
9381
+
9382
+ // Setter
9383
+ if (type === 'lib' || type === 'jq' || (module && module.fn && module.fn.jquery)) {
9214
9384
  $ = module;
9215
9385
  }
9216
- else if (type == 'win' || module.document) {
9386
+ else if (type == 'win' || (module && module.document)) {
9217
9387
  window = module;
9218
9388
  document = module.document;
9219
9389
  }
9220
- else if (type === 'datetime' || module.type === 'DateTime') {
9390
+ else if (type === 'datetime' || (module && module.type === 'DateTime')) {
9221
9391
  DataTable.DateTime = module;
9222
9392
  }
9393
+ else if (type === 'luxon' || (module && module.FixedOffsetZone)) {
9394
+ __luxon = module;
9395
+ }
9396
+ else if (type === 'moment' || (module && module.isMoment)) {
9397
+ __moment = module;
9398
+ }
9223
9399
  }
9224
9400
 
9225
9401
  /**
@@ -9628,7 +9804,7 @@
9628
9804
  * @type string
9629
9805
  * @default Version number
9630
9806
  */
9631
- DataTable.version = "2.0.8";
9807
+ DataTable.version = "2.1.2";
9632
9808
 
9633
9809
  /**
9634
9810
  * Private data store, containing all of the settings objects that are
@@ -10473,7 +10649,8 @@
10473
10649
  first: 'First',
10474
10650
  last: 'Last',
10475
10651
  next: 'Next',
10476
- previous: 'Previous'
10652
+ previous: 'Previous',
10653
+ number: ''
10477
10654
  }
10478
10655
  },
10479
10656
 
@@ -10653,6 +10830,10 @@
10653
10830
  },
10654
10831
 
10655
10832
 
10833
+ /** The initial data order is reversed when `desc` ordering */
10834
+ orderDescReverse: true,
10835
+
10836
+
10656
10837
  /**
10657
10838
  * This parameter allows you to have define the global filtering state at
10658
10839
  * initialisation time. As an object the `search` parameter must be
@@ -10701,7 +10882,7 @@
10701
10882
  * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
10702
10883
  * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
10703
10884
  */
10704
- "sPaginationType": "full_numbers",
10885
+ "sPaginationType": "",
10705
10886
 
10706
10887
 
10707
10888
  /**
@@ -10771,7 +10952,13 @@
10771
10952
  /**
10772
10953
  * Caption value
10773
10954
  */
10774
- "caption": null
10955
+ "caption": null,
10956
+
10957
+
10958
+ /**
10959
+ * For server-side processing - use the data from the DOM for the first draw
10960
+ */
10961
+ iDeferLoading: null
10775
10962
  };
10776
10963
 
10777
10964
  _fnHungarianMap( DataTable.defaults );
@@ -11714,7 +11901,10 @@
11714
11901
 
11715
11902
  captionNode: null,
11716
11903
 
11717
- colgroup: null
11904
+ colgroup: null,
11905
+
11906
+ /** Delay loading of data */
11907
+ deferLoading: null
11718
11908
  };
11719
11909
 
11720
11910
  /**
@@ -11738,7 +11928,7 @@
11738
11928
  },
11739
11929
 
11740
11930
  full: function () {
11741
- return [ 'first', 'previous', 'next', 'last' ];
11931
+ return [ 'first', 'previous', 'next', 'last' ];
11742
11932
  },
11743
11933
 
11744
11934
  numbers: function () {
@@ -11752,11 +11942,11 @@
11752
11942
  full_numbers: function () {
11753
11943
  return [ 'first', 'previous', 'numbers', 'next', 'last' ];
11754
11944
  },
11755
-
11945
+
11756
11946
  first_last: function () {
11757
11947
  return ['first', 'last'];
11758
11948
  },
11759
-
11949
+
11760
11950
  first_last_numbers: function () {
11761
11951
  return ['first', 'numbers', 'last'];
11762
11952
  },
@@ -11838,38 +12028,56 @@
11838
12028
  * to make working with DataTables a little bit easier.
11839
12029
  */
11840
12030
 
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 );
12031
+ /**
12032
+ * Common logic for moment, luxon or a date action.
12033
+ *
12034
+ * Happens after __mldObj, so don't need to call `resolveWindowsLibs` again
12035
+ */
12036
+ function __mld( dtLib, momentFn, luxonFn, dateFn, arg1 ) {
12037
+ if (__moment) {
12038
+ return dtLib[momentFn]( arg1 );
11849
12039
  }
11850
- else if (window.luxon) {
11851
- return dt[luxonFn]( arg1 );
12040
+ else if (__luxon) {
12041
+ return dtLib[luxonFn]( arg1 );
11852
12042
  }
11853
12043
 
11854
- return dateFn ? dt[dateFn]( arg1 ) : dt;
12044
+ return dateFn ? dtLib[dateFn]( arg1 ) : dtLib;
11855
12045
  }
11856
12046
 
11857
12047
 
11858
12048
  var __mlWarning = false;
12049
+ var __luxon; // Can be assigned in DateTeble.use()
12050
+ var __moment; // Can be assigned in DateTeble.use()
12051
+
12052
+ /**
12053
+ *
12054
+ */
12055
+ function resolveWindowLibs() {
12056
+ if (window.luxon && ! __luxon) {
12057
+ __luxon = window.luxon;
12058
+ }
12059
+
12060
+ if (window.moment && ! __moment) {
12061
+ __moment = window.moment;
12062
+ }
12063
+ }
12064
+
11859
12065
  function __mldObj (d, format, locale) {
11860
12066
  var dt;
11861
12067
 
11862
- if (window.moment) {
11863
- dt = window.moment.utc( d, format, locale, true );
12068
+ resolveWindowLibs();
12069
+
12070
+ if (__moment) {
12071
+ dt = __moment.utc( d, format, locale, true );
11864
12072
 
11865
12073
  if (! dt.isValid()) {
11866
12074
  return null;
11867
12075
  }
11868
12076
  }
11869
- else if (window.luxon) {
12077
+ else if (__luxon) {
11870
12078
  dt = format && typeof d === 'string'
11871
- ? window.luxon.DateTime.fromFormat( d, format )
11872
- : window.luxon.DateTime.fromISO( d );
12079
+ ? __luxon.DateTime.fromFormat( d, format )
12080
+ : __luxon.DateTime.fromISO( d );
11873
12081
 
11874
12082
  if (! dt.isValid) {
11875
12083
  return null;
@@ -11914,7 +12122,7 @@
11914
12122
  from = null;
11915
12123
  }
11916
12124
 
11917
- var typeName = 'datetime' + (to ? '-' + __mldFnName(to) : '');
12125
+ var typeName = 'datetime' + (to ? '-' + to : '');
11918
12126
 
11919
12127
  // Add type detection and sorting specific to this date format - we need to be able to identify
11920
12128
  // date type columns as such, rather than as numbers in extensions. Hence the need for this.
@@ -12017,7 +12225,7 @@
12017
12225
 
12018
12226
  // Formatted date time detection - use by declaring the formats you are going to use
12019
12227
  DataTable.datetime = function ( format, locale ) {
12020
- var typeName = 'datetime-detect-' + __mldFnName(format);
12228
+ var typeName = 'datetime-' + format;
12021
12229
 
12022
12230
  if (! locale) {
12023
12231
  locale = 'en';
@@ -12172,27 +12380,20 @@
12172
12380
  var setProp = function(prop, propVal) {
12173
12381
  _extTypes[prop][name] = propVal;
12174
12382
  };
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});
12383
+ var setDetect = function (detect) {
12384
+ // `detect` can be a function or an object - we set a name
12385
+ // property for either - that is used for the detection
12386
+ Object.defineProperty(detect, "name", {value: name});
12186
12387
 
12187
- var idx = _extTypes.detect.findIndex(function (fn) {
12188
- return fn.name === name;
12388
+ var idx = _extTypes.detect.findIndex(function (item) {
12389
+ return item.name === name;
12189
12390
  });
12190
12391
 
12191
12392
  if (idx === -1) {
12192
- _extTypes.detect.unshift(cb);
12393
+ _extTypes.detect.unshift(detect);
12193
12394
  }
12194
12395
  else {
12195
- _extTypes.detect.splice(idx, 1, cb);
12396
+ _extTypes.detect.splice(idx, 1, detect);
12196
12397
  }
12197
12398
  };
12198
12399
  var setOrder = function (obj) {
@@ -12252,6 +12453,19 @@
12252
12453
  });
12253
12454
  };
12254
12455
 
12456
+ var __diacriticSort = function (a, b) {
12457
+ a = a.toString().toLowerCase();
12458
+ b = b.toString().toLowerCase();
12459
+
12460
+ // Checked for `navigator.languages` support in `oneOf` so this code can't execute in old
12461
+ // Safari and thus can disable this check
12462
+ // eslint-disable-next-line compat/compat
12463
+ return a.localeCompare(b, navigator.languages[0] || navigator.language, {
12464
+ numeric: true,
12465
+ ignorePunctuation: true,
12466
+ });
12467
+ }
12468
+
12255
12469
  //
12256
12470
  // Built in data types
12257
12471
  //
@@ -12276,11 +12490,38 @@
12276
12490
  search: _filterString(false, true)
12277
12491
  });
12278
12492
 
12493
+ DataTable.type('string-utf8', {
12494
+ detect: {
12495
+ allOf: function ( d ) {
12496
+ return true;
12497
+ },
12498
+ oneOf: function ( d ) {
12499
+ // At least one data point must contain a non-ASCII character
12500
+ // This line will also check if navigator.languages is supported or not. If not (Safari 10.0-)
12501
+ // this data type won't be supported.
12502
+ // eslint-disable-next-line compat/compat
12503
+ return ! _empty( d ) && navigator.languages && typeof d === 'string' && d.match(/[^\x00-\x7F]/);
12504
+ }
12505
+ },
12506
+ order: {
12507
+ asc: __diacriticSort,
12508
+ desc: function (a, b) {
12509
+ return __diacriticSort(a, b) * -1;
12510
+ }
12511
+ },
12512
+ search: _filterString(false, true)
12513
+ });
12514
+
12279
12515
 
12280
12516
  DataTable.type('html', {
12281
- detect: function ( d ) {
12282
- return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
12283
- 'html' : null;
12517
+ detect: {
12518
+ allOf: function ( d ) {
12519
+ return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1);
12520
+ },
12521
+ oneOf: function ( d ) {
12522
+ // At least one data point must contain a `<`
12523
+ return ! _empty( d ) && typeof d === 'string' && d.indexOf('<') !== -1;
12524
+ }
12284
12525
  },
12285
12526
  order: {
12286
12527
  pre: function ( a ) {
@@ -12297,16 +12538,21 @@
12297
12538
 
12298
12539
  DataTable.type('date', {
12299
12540
  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;
12541
+ detect: {
12542
+ allOf: function ( d ) {
12543
+ // V8 tries _very_ hard to make a string passed into `Date.parse()`
12544
+ // valid, so we need to use a regex to restrict date formats. Use a
12545
+ // plug-in for anything other than ISO8601 style strings
12546
+ if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
12547
+ return null;
12548
+ }
12549
+ var parsed = Date.parse(d);
12550
+ return (parsed !== null && !isNaN(parsed)) || _empty(d);
12551
+ },
12552
+ oneOf: function ( d ) {
12553
+ // At least one entry must be a date or a string with a date
12554
+ return (d instanceof Date) || (typeof d === 'string' && _re_date.test(d));
12307
12555
  }
12308
- var parsed = Date.parse(d);
12309
- return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
12310
12556
  },
12311
12557
  order: {
12312
12558
  pre: function ( d ) {
@@ -12319,10 +12565,16 @@
12319
12565
 
12320
12566
  DataTable.type('html-num-fmt', {
12321
12567
  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;
12568
+ detect: {
12569
+ allOf: function ( d, settings ) {
12570
+ var decimal = settings.oLanguage.sDecimal;
12571
+ return _htmlNumeric( d, decimal, true, false );
12572
+ },
12573
+ oneOf: function (d, settings) {
12574
+ // At least one data point must contain a numeric value
12575
+ var decimal = settings.oLanguage.sDecimal;
12576
+ return _htmlNumeric( d, decimal, true, false );
12577
+ }
12326
12578
  },
12327
12579
  order: {
12328
12580
  pre: function ( d, s ) {
@@ -12336,10 +12588,16 @@
12336
12588
 
12337
12589
  DataTable.type('html-num', {
12338
12590
  className: 'dt-type-numeric',
12339
- detect: function ( d, settings )
12340
- {
12341
- var decimal = settings.oLanguage.sDecimal;
12342
- return _htmlNumeric( d, decimal ) ? 'html-num' : null;
12591
+ detect: {
12592
+ allOf: function ( d, settings ) {
12593
+ var decimal = settings.oLanguage.sDecimal;
12594
+ return _htmlNumeric( d, decimal, false, true );
12595
+ },
12596
+ oneOf: function (d, settings) {
12597
+ // At least one data point must contain a numeric value
12598
+ var decimal = settings.oLanguage.sDecimal;
12599
+ return _htmlNumeric( d, decimal, false, false );
12600
+ }
12343
12601
  },
12344
12602
  order: {
12345
12603
  pre: function ( d, s ) {
@@ -12353,10 +12611,16 @@
12353
12611
 
12354
12612
  DataTable.type('num-fmt', {
12355
12613
  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;
12614
+ detect: {
12615
+ allOf: function ( d, settings ) {
12616
+ var decimal = settings.oLanguage.sDecimal;
12617
+ return _isNumber( d, decimal, true, true );
12618
+ },
12619
+ oneOf: function (d, settings) {
12620
+ // At least one data point must contain a numeric value
12621
+ var decimal = settings.oLanguage.sDecimal;
12622
+ return _isNumber( d, decimal, true, false );
12623
+ }
12360
12624
  },
12361
12625
  order: {
12362
12626
  pre: function ( d, s ) {
@@ -12369,10 +12633,16 @@
12369
12633
 
12370
12634
  DataTable.type('num', {
12371
12635
  className: 'dt-type-numeric',
12372
- detect: function ( d, settings )
12373
- {
12374
- var decimal = settings.oLanguage.sDecimal;
12375
- return _isNumber( d, decimal ) ? 'num' : null;
12636
+ detect: {
12637
+ allOf: function ( d, settings ) {
12638
+ var decimal = settings.oLanguage.sDecimal;
12639
+ return _isNumber( d, decimal, false, true );
12640
+ },
12641
+ oneOf: function (d, settings) {
12642
+ // At least one data point must contain a numeric value
12643
+ var decimal = settings.oLanguage.sDecimal;
12644
+ return _isNumber( d, decimal, false, false );
12645
+ }
12376
12646
  },
12377
12647
  order: {
12378
12648
  pre: function (d, s) {
@@ -12456,11 +12726,12 @@
12456
12726
  // `DT` namespace will allow the event to be removed automatically
12457
12727
  // on destroy, while the `dt` namespaced event is the one we are
12458
12728
  // listening for
12459
- $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting ) {
12729
+ $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) {
12460
12730
  if ( settings !== ctx ) { // need to check this this is the host
12461
12731
  return; // table, not a nested one
12462
12732
  }
12463
12733
 
12734
+ var i;
12464
12735
  var orderClasses = classes.order;
12465
12736
  var columns = ctx.api.columns( cell );
12466
12737
  var col = settings.aoColumns[columns.flatten()[0]];
@@ -12468,9 +12739,8 @@
12468
12739
  var ariaType = '';
12469
12740
  var indexes = columns.indexes();
12470
12741
  var sortDirs = columns.orderable(true).flatten();
12471
- var orderedColumns = ',' + sorting.map( function (val) {
12472
- return val.col;
12473
- } ).join(',') + ',';
12742
+ var sorting = ctx.sortDetails;
12743
+ var orderedColumns = _pluck(sorting, 'col');
12474
12744
 
12475
12745
  cell
12476
12746
  .removeClass(
@@ -12480,10 +12750,18 @@
12480
12750
  .toggleClass( orderClasses.none, ! orderable )
12481
12751
  .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
12482
12752
  .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
12753
+
12754
+ // Determine if all of the columns that this cell covers are included in the
12755
+ // current ordering
12756
+ var isOrdering = true;
12483
12757
 
12484
- var sortIdx = orderedColumns.indexOf( ',' + indexes.toArray().join(',') + ',' );
12758
+ for (i=0; i<indexes.length; i++) {
12759
+ if (! orderedColumns.includes(indexes[i])) {
12760
+ isOrdering = false;
12761
+ }
12762
+ }
12485
12763
 
12486
- if ( sortIdx !== -1 ) {
12764
+ if ( isOrdering ) {
12487
12765
  // Get the ordering direction for the columns under this cell
12488
12766
  // Note that it is possible for a cell to be asc and desc sorting
12489
12767
  // (column spanning cells)
@@ -12495,8 +12773,19 @@
12495
12773
  );
12496
12774
  }
12497
12775
 
12498
- // The ARIA spec says that only one column should be marked with aria-sort
12499
- if ( sortIdx === 0 ) {
12776
+ // Find the first visible column that has ordering applied to it - it get's
12777
+ // the aria information, as the ARIA spec says that only one column should
12778
+ // be marked with aria-sort
12779
+ var firstVis = -1; // column index
12780
+
12781
+ for (i=0; i<orderedColumns.length; i++) {
12782
+ if (settings.aoColumns[orderedColumns[i]].bVisible) {
12783
+ firstVis = orderedColumns[i];
12784
+ break;
12785
+ }
12786
+ }
12787
+
12788
+ if (indexes[0] == firstVis) {
12500
12789
  var firstSort = sorting[0];
12501
12790
  var sortOrder = col.asSorting;
12502
12791
 
@@ -12514,6 +12803,7 @@
12514
12803
  : col.ariaTitle
12515
12804
  );
12516
12805
 
12806
+ // Make the headers tab-able for keyboard navigation
12517
12807
  if (orderable) {
12518
12808
  cell.find('.dt-column-title').attr('role', 'button');
12519
12809
  cell.attr('tabindex', 0)
@@ -12524,23 +12814,40 @@
12524
12814
 
12525
12815
  layout: {
12526
12816
  _: function ( settings, container, items ) {
12817
+ var classes = settings.oClasses.layout;
12527
12818
  var row = $('<div/>')
12528
- .addClass('dt-layout-row')
12819
+ .attr('id', items.id || null)
12820
+ .addClass(items.className || classes.row)
12529
12821
  .appendTo( container );
12530
12822
 
12531
12823
  $.each( items, function (key, val) {
12532
- var klass = ! val.table ?
12533
- 'dt-'+key+' ' :
12534
- '';
12824
+ if (key === 'id' || key === 'className') {
12825
+ return;
12826
+ }
12827
+
12828
+ var klass = '';
12535
12829
 
12536
12830
  if (val.table) {
12537
- row.addClass('dt-layout-table');
12831
+ row.addClass(classes.tableRow);
12832
+ klass += classes.tableCell + ' ';
12833
+ }
12834
+
12835
+ if (key === 'start') {
12836
+ klass += classes.start;
12837
+ }
12838
+ else if (key === 'end') {
12839
+ klass += classes.end;
12840
+ }
12841
+ else {
12842
+ klass += classes.full;
12538
12843
  }
12539
12844
 
12540
12845
  $('<div/>')
12541
12846
  .attr({
12542
12847
  id: val.id || null,
12543
- "class": 'dt-layout-cell '+klass+(val.className || '')
12848
+ "class": val.className
12849
+ ? val.className
12850
+ : classes.cell + ' ' + klass
12544
12851
  })
12545
12852
  .append( val.contents )
12546
12853
  .appendTo( row );
@@ -12564,6 +12871,25 @@
12564
12871
  }
12565
12872
  };
12566
12873
 
12874
+ function _divProp(el, prop, val) {
12875
+ if (val) {
12876
+ el[prop] = val;
12877
+ }
12878
+ }
12879
+
12880
+ DataTable.feature.register( 'div', function ( settings, opts ) {
12881
+ var n = $('<div>')[0];
12882
+
12883
+ if (opts) {
12884
+ _divProp(n, 'className', opts.className);
12885
+ _divProp(n, 'id', opts.id);
12886
+ _divProp(n, 'innerHTML', opts.html);
12887
+ _divProp(n, 'textContent', opts.text);
12888
+ }
12889
+
12890
+ return n;
12891
+ } );
12892
+
12567
12893
  DataTable.feature.register( 'info', function ( settings, opts ) {
12568
12894
  // For compatibility with the legacy `info` top level option
12569
12895
  if (! settings.oFeatures.bInfo) {
@@ -12663,6 +12989,7 @@
12663
12989
 
12664
12990
  opts = $.extend({
12665
12991
  placeholder: language.sSearchPlaceholder,
12992
+ processing: false,
12666
12993
  text: language.sSearch
12667
12994
  }, opts);
12668
12995
 
@@ -12706,13 +13033,15 @@
12706
13033
 
12707
13034
  /* Now do the filter */
12708
13035
  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 );
13036
+ _fnProcessingRun(settings, opts.processing, function () {
13037
+ previousSearch.search = val;
13038
+
13039
+ _fnFilterComplete( settings, previousSearch );
13040
+
13041
+ // Need to redraw, without resorting
13042
+ settings._iDisplayStart = 0;
13043
+ _fnDraw( settings );
13044
+ });
12716
13045
  }
12717
13046
  };
12718
13047
 
@@ -12770,17 +13099,17 @@
12770
13099
  opts = $.extend({
12771
13100
  buttons: DataTable.ext.pager.numbers_length,
12772
13101
  type: settings.sPaginationType,
12773
- boundaryNumbers: true
13102
+ boundaryNumbers: true,
13103
+ firstLast: true,
13104
+ previousNext: true,
13105
+ numbers: true
12774
13106
  }, opts);
12775
13107
 
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 );
13108
+ var host = $('<div/>')
13109
+ .addClass(settings.oClasses.paging.container + (opts.type ? ' paging_' + opts.type : ''))
13110
+ .append('<nav>');
12782
13111
  var draw = function () {
12783
- _pagingDraw(settings, host, opts);
13112
+ _pagingDraw(settings, host.children(), opts);
12784
13113
  };
12785
13114
 
12786
13115
  settings.aoDrawCallback.push(draw);
@@ -12791,13 +13120,39 @@
12791
13120
  return host;
12792
13121
  }, 'p' );
12793
13122
 
13123
+ /**
13124
+ * Dynamically create the button type array based on the configuration options.
13125
+ * This will only happen if the paging type is not defined.
13126
+ */
13127
+ function _pagingDynamic(opts) {
13128
+ var out = [];
13129
+
13130
+ if (opts.numbers) {
13131
+ out.push('numbers');
13132
+ }
13133
+
13134
+ if (opts.previousNext) {
13135
+ out.unshift('previous');
13136
+ out.push('next');
13137
+ }
13138
+
13139
+ if (opts.firstLast) {
13140
+ out.unshift('first');
13141
+ out.push('last');
13142
+ }
13143
+
13144
+ return out;
13145
+ }
13146
+
12794
13147
  function _pagingDraw(settings, host, opts) {
12795
13148
  if (! settings._bInitComplete) {
12796
13149
  return;
12797
13150
  }
12798
13151
 
12799
13152
  var
12800
- plugin = DataTable.ext.pager[ opts.type ],
13153
+ plugin = opts.type
13154
+ ? DataTable.ext.pager[ opts.type ]
13155
+ : _pagingDynamic,
12801
13156
  aria = settings.oLanguage.oAria.paginate || {},
12802
13157
  start = settings._iDisplayStart,
12803
13158
  len = settings._iDisplayLength,
@@ -12805,7 +13160,7 @@
12805
13160
  all = len === -1,
12806
13161
  page = all ? 0 : Math.ceil( start / len ),
12807
13162
  pages = all ? 1 : Math.ceil( visRecords / len ),
12808
- buttons = plugin()
13163
+ buttons = plugin(opts)
12809
13164
  .map(function (val) {
12810
13165
  return val === 'numbers'
12811
13166
  ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers)
@@ -12827,14 +13182,24 @@
12827
13182
  btnInfo.disabled
12828
13183
  );
12829
13184
 
13185
+ var ariaLabel = typeof button === 'string'
13186
+ ? aria[ button ]
13187
+ : aria.number
13188
+ ? aria.number + (button+1)
13189
+ : null;
13190
+
12830
13191
  // Common attributes
12831
13192
  $(btn.clicker).attr({
12832
13193
  'aria-controls': settings.sTableId,
12833
13194
  'aria-disabled': btnInfo.disabled ? 'true' : null,
12834
13195
  'aria-current': btnInfo.active ? 'page' : null,
12835
- 'aria-label': aria[ button ],
13196
+ 'aria-label': ariaLabel,
12836
13197
  'data-dt-idx': button,
12837
- 'tabIndex': btnInfo.disabled ? -1 : settings.iTabIndex,
13198
+ 'tabIndex': btnInfo.disabled
13199
+ ? -1
13200
+ : settings.iTabIndex
13201
+ ? settings.iTabIndex
13202
+ : null, // `0` doesn't need a tabIndex since it is the default
12838
13203
  });
12839
13204
 
12840
13205
  if (typeof button !== 'number') {
@@ -12868,10 +13233,10 @@
12868
13233
  // height of the buttons and the container.
12869
13234
  if (
12870
13235
  buttonEls.length && // any buttons
12871
- opts.numbers > 1 && // prevent infinite
13236
+ opts.buttons > 1 && // prevent infinite
12872
13237
  $(host).height() >= ($(buttonEls[0]).outerHeight() * 2) - 10
12873
13238
  ) {
12874
- _pagingDraw(settings, host, $.extend({}, opts, { numbers: opts.numbers - 2 }));
13239
+ _pagingDraw(settings, host, $.extend({}, opts, { buttons: opts.buttons - 2 }));
12875
13240
  }
12876
13241
  }
12877
13242
 
@@ -13073,10 +13438,11 @@
13073
13438
  }
13074
13439
 
13075
13440
  // Wrapper element - use a span as a holder for where the select will go
13441
+ var tmpId = 'tmp-' + (+new Date())
13076
13442
  var div = $('<div/>')
13077
13443
  .addClass( classes.container )
13078
13444
  .append(
13079
- str.replace( '_MENU_', '<span></span>' )
13445
+ str.replace( '_MENU_', '<span id="'+tmpId+'"></span>' )
13080
13446
  );
13081
13447
 
13082
13448
  // Save text node content for macro updating
@@ -13119,7 +13485,7 @@
13119
13485
  __lengthCounter++;
13120
13486
 
13121
13487
  // Swap in the select list
13122
- div.find('span').replaceWith(select);
13488
+ div.find('#' + tmpId).replaceWith(select);
13123
13489
 
13124
13490
  // Can't use `select` variable as user might provide their own and the
13125
13491
  // reference is broken by the use of outerHTML