datatables.net 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/dataTables.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! DataTables 2.2.1
1
+ /*! DataTables 2.3.0
2
2
  * © SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
@@ -54,15 +54,19 @@ var DataTable = function ( selector, options )
54
54
  var defaults = DataTable.defaults;
55
55
  var $this = $(this);
56
56
 
57
-
58
- /* Sanity check */
57
+ // Sanity check
59
58
  if ( this.nodeName.toLowerCase() != 'table' )
60
59
  {
61
60
  _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
62
61
  return;
63
62
  }
64
63
 
65
- $(this).trigger( 'options.dt', oInit );
64
+ // Special case for options
65
+ if (oInit.on && oInit.on.options) {
66
+ _fnListener($this, 'options', oInit.on.options);
67
+ }
68
+
69
+ $this.trigger( 'options.dt', oInit );
66
70
 
67
71
  /* Backwards compatibility for the defaults */
68
72
  _fnCompatOpts( defaults );
@@ -201,6 +205,9 @@ var DataTable = function ( selector, options )
201
205
  "caption",
202
206
  "layout",
203
207
  "orderDescReverse",
208
+ "orderIndicators",
209
+ "orderHandler",
210
+ "titleRow",
204
211
  "typeDetect",
205
212
  [ "iCookieDuration", "iStateDuration" ], // backwards compat
206
213
  [ "oSearch", "oPreviousSearch" ],
@@ -229,6 +236,13 @@ var DataTable = function ( selector, options )
229
236
 
230
237
  oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
231
238
 
239
+ // Add event listeners
240
+ if (oInit.on) {
241
+ Object.keys(oInit.on).forEach(function (key) {
242
+ _fnListener($this, key, oInit.on[key]);
243
+ });
244
+ }
245
+
232
246
  /* Browser support detection */
233
247
  _fnBrowserDetect( oSettings );
234
248
 
@@ -289,7 +303,7 @@ var DataTable = function ( selector, options )
289
303
  /* HTML5 attribute detection - build an mData object automatically if the
290
304
  * attributes are found
291
305
  */
292
- var rowOne = $this.children('tbody').find('tr').eq(0);
306
+ var rowOne = $this.children('tbody').find('tr:first-child').eq(0);
293
307
 
294
308
  if ( rowOne.length ) {
295
309
  var a = function ( cell, name ) {
@@ -447,6 +461,13 @@ var DataTable = function ( selector, options )
447
461
  * @namespace
448
462
  */
449
463
  DataTable.ext = _ext = {
464
+ /**
465
+ * DataTables build type (expanded by the download builder)
466
+ *
467
+ * @type string
468
+ */
469
+ builder: "-source-",
470
+
450
471
  /**
451
472
  * Buttons. For use with the Buttons extension for DataTables. This is
452
473
  * defined here so other extensions can define buttons regardless of load
@@ -459,20 +480,20 @@ DataTable.ext = _ext = {
459
480
 
460
481
 
461
482
  /**
462
- * Element class names
483
+ * ColumnControl buttons and content
463
484
  *
464
485
  * @type object
465
- * @default {}
466
486
  */
467
- classes: {},
487
+ ccContent: {},
468
488
 
469
489
 
470
490
  /**
471
- * DataTables build type (expanded by the download builder)
491
+ * Element class names
472
492
  *
473
- * @type string
493
+ * @type object
494
+ * @default {}
474
495
  */
475
- builder: "-source-",
496
+ classes: {},
476
497
 
477
498
 
478
499
  /**
@@ -1840,6 +1861,22 @@ function _fnCompatOpts ( init )
1840
1861
  init.scrollX = init.scrollX ? '100%' : '';
1841
1862
  }
1842
1863
 
1864
+ // Objects for ordering
1865
+ if ( typeof init.bSort === 'object' ) {
1866
+ init.orderIndicators = init.bSort.indicators !== undefined ? init.bSort.indicators : true;
1867
+ init.orderHandler = init.bSort.handler !== undefined ? init.bSort.handler : true;
1868
+ init.bSort = true;
1869
+ }
1870
+ else if (init.bSort === false) {
1871
+ init.orderIndicators = false;
1872
+ init.orderHandler = false;
1873
+ }
1874
+
1875
+ // Which cells are the title cells?
1876
+ if (typeof init.bSortCellsTop === 'boolean') {
1877
+ init.titleRow = init.bSortCellsTop;
1878
+ }
1879
+
1843
1880
  // Column search objects are in an array, so it needs to be converted
1844
1881
  // element by element
1845
1882
  var searchCols = init.aoSearchCols;
@@ -3217,7 +3254,7 @@ function _fnBuildHead( settings, side )
3217
3254
  * @param {*} settings DataTables settings
3218
3255
  * @param {*} source Source layout array
3219
3256
  * @param {*} incColumns What columns should be included
3220
- * @returns Layout array
3257
+ * @returns Layout array in column index order
3221
3258
  */
3222
3259
  function _fnHeaderLayout( settings, source, incColumns )
3223
3260
  {
@@ -3513,10 +3550,9 @@ function _emptyRow ( settings ) {
3513
3550
  var zero = oLang.sZeroRecords;
3514
3551
  var dataSrc = _fnDataSource( settings );
3515
3552
 
3516
- if (
3517
- (settings.iDraw < 1 && dataSrc === 'ssp') ||
3518
- (settings.iDraw <= 1 && dataSrc === 'ajax')
3519
- ) {
3553
+ // Make use of the fact that settings.json is only set once the initial data has
3554
+ // been loaded. Show loading when that isn't the case
3555
+ if ((dataSrc === 'ssp' || dataSrc === 'ajax') && ! settings.json) {
3520
3556
  zero = oLang.sLoadingRecords;
3521
3557
  }
3522
3558
  else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 )
@@ -3886,6 +3922,7 @@ function _fnDetectHeader ( settings, thead, write )
3886
3922
  var rows = $(thead).children('tr');
3887
3923
  var row, cell;
3888
3924
  var i, k, l, iLen, shifted, column, colspan, rowspan;
3925
+ var titleRow = settings.titleRow;
3889
3926
  var isHeader = thead && thead.nodeName.toLowerCase() === 'thead';
3890
3927
  var layout = [];
3891
3928
  var unique;
@@ -3914,6 +3951,7 @@ function _fnDetectHeader ( settings, thead, write )
3914
3951
  cell.nodeName.toUpperCase() == 'TH'
3915
3952
  ) {
3916
3953
  var cols = [];
3954
+ var jqCell = $(cell);
3917
3955
 
3918
3956
  // Get the col and rowspan attributes from the DOM and sanitise them
3919
3957
  colspan = cell.getAttribute('colspan') * 1;
@@ -3934,7 +3972,7 @@ function _fnDetectHeader ( settings, thead, write )
3934
3972
  if ( write ) {
3935
3973
  if (unique) {
3936
3974
  // Allow column options to be set from HTML attributes
3937
- _fnColumnOptions( settings, shifted, $(cell).data() );
3975
+ _fnColumnOptions( settings, shifted, jqCell.data() );
3938
3976
 
3939
3977
  // Get the width for the column. This can be defined from the
3940
3978
  // width attribute, style attribute or `columns.width` option
@@ -3951,7 +3989,14 @@ function _fnDetectHeader ( settings, thead, write )
3951
3989
  // Column title handling - can be user set, or read from the DOM
3952
3990
  // This happens before the render, so the original is still in place
3953
3991
  if ( columnDef.sTitle !== null && ! columnDef.autoTitle ) {
3954
- cell.innerHTML = columnDef.sTitle;
3992
+ if (
3993
+ (titleRow === true && i === 0) || // top row
3994
+ (titleRow === false && i === rows.length -1) || // bottom row
3995
+ (titleRow === i) || // specific row
3996
+ (titleRow === null)
3997
+ ) {
3998
+ cell.innerHTML = columnDef.sTitle;
3999
+ }
3955
4000
  }
3956
4001
 
3957
4002
  if (! columnDef.sTitle && unique) {
@@ -3969,12 +4014,12 @@ function _fnDetectHeader ( settings, thead, write )
3969
4014
  // Fall back to the aria-label attribute on the table header if no ariaTitle is
3970
4015
  // provided.
3971
4016
  if (! columnDef.ariaTitle) {
3972
- columnDef.ariaTitle = $(cell).attr("aria-label") || columnDef.sTitle;
4017
+ columnDef.ariaTitle = jqCell.attr("aria-label") || columnDef.sTitle;
3973
4018
  }
3974
4019
 
3975
4020
  // Column specific class names
3976
4021
  if ( columnDef.className ) {
3977
- $(cell).addClass( columnDef.className );
4022
+ jqCell.addClass( columnDef.className );
3978
4023
  }
3979
4024
  }
3980
4025
 
@@ -3986,11 +4031,28 @@ function _fnDetectHeader ( settings, thead, write )
3986
4031
  .appendTo(cell);
3987
4032
  }
3988
4033
 
3989
- if ( isHeader && $('span.dt-column-order', cell).length === 0) {
4034
+ if (
4035
+ settings.orderIndicators &&
4036
+ isHeader &&
4037
+ jqCell.filter(':not([data-dt-order=disable])').length !== 0 &&
4038
+ jqCell.parent(':not([data-dt-order=disable])').length !== 0 &&
4039
+ $('span.dt-column-order', cell).length === 0
4040
+ ) {
3990
4041
  $('<span>')
3991
4042
  .addClass('dt-column-order')
3992
4043
  .appendTo(cell);
3993
4044
  }
4045
+
4046
+ // We need to wrap the elements in the header in another element to use flexbox
4047
+ // layout for those elements
4048
+ var headerFooter = isHeader ? 'header' : 'footer';
4049
+
4050
+ if ( $('span.dt-column-' + headerFooter, cell).length === 0) {
4051
+ $('<div>')
4052
+ .addClass('dt-column-' + headerFooter)
4053
+ .append(cell.childNodes)
4054
+ .appendTo(cell);
4055
+ }
3994
4056
  }
3995
4057
 
3996
4058
  // If there is col / rowspan, copy the information into the layout grid
@@ -4141,6 +4203,11 @@ function _fnBuildAjax( oSettings, data, fn )
4141
4203
  // Allow plug-ins and external processes to modify the data
4142
4204
  _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data, baseAjax], true );
4143
4205
 
4206
+ // Custom Ajax option to submit the parameters as a JSON string
4207
+ if (baseAjax.submitAs === 'json' && typeof data === 'object') {
4208
+ baseAjax.data = JSON.stringify(data);
4209
+ }
4210
+
4144
4211
  if ( typeof ajax === 'function' )
4145
4212
  {
4146
4213
  // Is a function - let the caller define what needs to be done
@@ -5509,6 +5576,15 @@ function _fnCalculateColumnWidths ( settings )
5509
5576
  // This flag allows the above to be satisfied.
5510
5577
  var first = $(settings.nTableWrapper).is(':visible');
5511
5578
 
5579
+ // Use an empty div to attach the observer so it isn't impacted by height changes
5580
+ var resizer = $('<div>')
5581
+ .css({
5582
+ width: '100%',
5583
+ height: 0
5584
+ })
5585
+ .addClass('dt-autosize')
5586
+ .appendTo(settings.nTableWrapper);
5587
+
5512
5588
  settings.resizeObserver = new ResizeObserver(function (e) {
5513
5589
  if (first) {
5514
5590
  first = false;
@@ -5518,7 +5594,7 @@ function _fnCalculateColumnWidths ( settings )
5518
5594
  }
5519
5595
  });
5520
5596
 
5521
- settings.resizeObserver.observe(settings.nTableWrapper);
5597
+ settings.resizeObserver.observe(resizer[0]);
5522
5598
  }
5523
5599
  else {
5524
5600
  // For old browsers, the best we can do is listen for a window resize
@@ -5632,24 +5708,30 @@ function _colGroup( settings ) {
5632
5708
  function _fnSortInit( settings ) {
5633
5709
  var target = settings.nTHead;
5634
5710
  var headerRows = target.querySelectorAll('tr');
5635
- var legacyTop = settings.bSortCellsTop;
5711
+ var titleRow = settings.titleRow;
5636
5712
  var notSelector = ':not([data-dt-order="disable"]):not([data-dt-order="icon-only"])';
5637
5713
 
5638
5714
  // Legacy support for `orderCellsTop`
5639
- if (legacyTop === true) {
5715
+ if (titleRow === true) {
5640
5716
  target = headerRows[0];
5641
5717
  }
5642
- else if (legacyTop === false) {
5718
+ else if (titleRow === false) {
5643
5719
  target = headerRows[ headerRows.length - 1 ];
5644
5720
  }
5721
+ else if (titleRow !== null) {
5722
+ target = headerRows[titleRow];
5723
+ }
5724
+ // else - all rows
5645
5725
 
5646
- _fnSortAttachListener(
5647
- settings,
5648
- target,
5649
- target === settings.nTHead
5650
- ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector
5651
- : 'th'+notSelector+', td'+notSelector
5652
- );
5726
+ if (settings.orderHandler) {
5727
+ _fnSortAttachListener(
5728
+ settings,
5729
+ target,
5730
+ target === settings.nTHead
5731
+ ? 'tr'+notSelector+' th'+notSelector+', tr'+notSelector+' td'+notSelector
5732
+ : 'th'+notSelector+', td'+notSelector
5733
+ );
5734
+ }
5653
5735
 
5654
5736
  // Need to resolve the user input array into our internal structure
5655
5737
  var order = [];
@@ -5664,7 +5746,9 @@ function _fnSortAttachListener(settings, node, selector, column, callback) {
5664
5746
  var run = false;
5665
5747
  var columns = column === undefined
5666
5748
  ? _fnColumnsFromHeader( e.target )
5667
- : [column];
5749
+ : Array.isArray(column)
5750
+ ? column
5751
+ : [column];
5668
5752
 
5669
5753
  if ( columns.length ) {
5670
5754
  for ( var i=0, ien=columns.length ; i<ien ; i++ ) {
@@ -5850,10 +5934,14 @@ function _fnSort ( oSettings, col, dir )
5850
5934
  displayMaster = oSettings.aiDisplayMaster,
5851
5935
  aSort;
5852
5936
 
5937
+ // Make sure the columns all have types defined
5938
+ _fnColumnTypes(oSettings);
5939
+
5853
5940
  // Allow a specific column to be sorted, which will _not_ alter the display
5854
5941
  // master
5855
5942
  if (col !== undefined) {
5856
5943
  var srcCol = oSettings.aoColumns[col];
5944
+
5857
5945
  aSort = [{
5858
5946
  src: col,
5859
5947
  col: col,
@@ -6283,16 +6371,19 @@ function _fnImplementState ( settings, s, callback) {
6283
6371
 
6284
6372
  // A column name was stored and should be used for restore
6285
6373
  if (typeof col[0] === 'string') {
6374
+ // Find the name from the current list of column names
6286
6375
  var idx = currentNames.indexOf(col[0]);
6287
6376
 
6288
- // Find the name from the current list of column names, or fallback to index 0
6289
- set[0] = idx >= 0
6290
- ? idx
6291
- : 0;
6377
+ if (idx < 0) {
6378
+ // If the column was not found ignore it and continue
6379
+ return;
6380
+ }
6381
+
6382
+ set[0] = idx;
6292
6383
  }
6293
6384
  else if (set[0] >= columns.length) {
6294
- // If a column name, but it is out of bounds, set to 0
6295
- set[0] = 0;
6385
+ // If the column index is out of bounds ignore it and continue
6386
+ return;
6296
6387
  }
6297
6388
 
6298
6389
  settings.aaSorting.push(set);
@@ -6705,6 +6796,23 @@ function _fnArrayApply(arr, data) {
6705
6796
  }
6706
6797
  }
6707
6798
 
6799
+ /**
6800
+ * Add one or more listeners to the table
6801
+ *
6802
+ * @param {*} that JQ for the table
6803
+ * @param {*} name Event name
6804
+ * @param {*} src Listener(s)
6805
+ */
6806
+ function _fnListener(that, name, src) {
6807
+ if (!Array.isArray(src)) {
6808
+ src = [src];
6809
+ }
6810
+
6811
+ for (i=0 ; i<src.length ; i++) {
6812
+ that.on(name + '.dt', src[i]);
6813
+ }
6814
+ }
6815
+
6708
6816
 
6709
6817
 
6710
6818
  /**
@@ -7361,12 +7469,24 @@ _api_register( 'table()', function ( selector ) {
7361
7469
  ['footer', 'aoFooter'],
7362
7470
  ].forEach(function (item) {
7363
7471
  _api_register( 'table().' + item[0] + '.structure()' , function (selector) {
7364
- var indexes = this.columns(selector).indexes().flatten();
7472
+ var indexes = this.columns(selector).indexes().flatten().toArray();
7365
7473
  var ctx = this.context[0];
7366
-
7367
- return _fnHeaderLayout(ctx, ctx[item[1]], indexes);
7368
- } );
7369
- })
7474
+ var structure = _fnHeaderLayout(ctx, ctx[item[1]], indexes);
7475
+
7476
+ // The structure is in column index order - but from this method we want the return to be
7477
+ // in the columns() selector API order. In order to do that we need to map from one form
7478
+ // to the other
7479
+ var orderedIndexes = indexes.slice().sort(function (a, b) {
7480
+ return a - b;
7481
+ });
7482
+
7483
+ return structure.map(function (row) {
7484
+ return indexes.map(function (colIdx) {
7485
+ return row[orderedIndexes.indexOf(colIdx)];
7486
+ });
7487
+ });
7488
+ });
7489
+ });
7370
7490
 
7371
7491
 
7372
7492
  _api_registerPlural( 'tables().containers()', 'table().container()' , function () {
@@ -7715,7 +7835,7 @@ var _selector_run = function ( type, selector, selectFn, settings, opts )
7715
7835
  {
7716
7836
  var
7717
7837
  out = [], res,
7718
- a, i, ien, j, jen,
7838
+ i, ien,
7719
7839
  selectorType = typeof selector;
7720
7840
 
7721
7841
  // Can't just check for isArray here, as an API or jQuery instance might be
@@ -7725,22 +7845,15 @@ var _selector_run = function ( type, selector, selectFn, settings, opts )
7725
7845
  }
7726
7846
 
7727
7847
  for ( i=0, ien=selector.length ; i<ien ; i++ ) {
7728
- // Only split on simple strings - complex expressions will be jQuery selectors
7729
- a = selector[i] && selector[i].split && ! selector[i].match(/[[(:]/) ?
7730
- selector[i].split(',') :
7731
- [ selector[i] ];
7848
+ res = selectFn( typeof selector[i] === 'string' ? selector[i].trim() : selector[i] );
7732
7849
 
7733
- for ( j=0, jen=a.length ; j<jen ; j++ ) {
7734
- res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] );
7735
-
7736
- // Remove empty items
7737
- res = res.filter( function (item) {
7738
- return item !== null && item !== undefined;
7739
- });
7850
+ // Remove empty items
7851
+ res = res.filter( function (item) {
7852
+ return item !== null && item !== undefined;
7853
+ });
7740
7854
 
7741
- if ( res && res.length ) {
7742
- out = out.concat( res );
7743
- }
7855
+ if ( res && res.length ) {
7856
+ out = out.concat( res );
7744
7857
  }
7745
7858
  }
7746
7859
 
@@ -7769,6 +7882,7 @@ var _selector_opts = function ( opts )
7769
7882
  }
7770
7883
 
7771
7884
  return $.extend( {
7885
+ columnOrder: 'implied',
7772
7886
  search: 'none',
7773
7887
  order: 'current',
7774
7888
  page: 'all'
@@ -8530,23 +8644,60 @@ var __columnData = function ( settings, column, r1, r2, rows, type ) {
8530
8644
 
8531
8645
  var __column_header = function ( settings, column, row ) {
8532
8646
  var header = settings.aoHeader;
8533
- var target = row !== undefined
8534
- ? row
8535
- : settings.bSortCellsTop // legacy support
8536
- ? 0
8537
- : header.length - 1;
8647
+ var titleRow = settings.titleRow;
8648
+ var target = null;
8649
+
8650
+ if (row !== undefined) {
8651
+ target = row;
8652
+ }
8653
+ else if (titleRow === true) { // legacy orderCellsTop support
8654
+ target = 0;
8655
+ }
8656
+ else if (titleRow === false) {
8657
+ target = header.length - 1;
8658
+ }
8659
+ else if (titleRow !== null) {
8660
+ target = titleRow;
8661
+ }
8662
+ else {
8663
+ // Automatic - find the _last_ unique cell from the top that is not empty (last for
8664
+ // backwards compatibility)
8665
+ for (var i=0 ; i<header.length ; i++) {
8666
+ if (header[i][column].unique && $('span.dt-column-title', header[i][column].cell).text()) {
8667
+ target = i;
8668
+ }
8669
+ }
8670
+
8671
+ if (target === null) {
8672
+ target = 0;
8673
+ }
8674
+ }
8538
8675
 
8539
8676
  return header[target][column].cell;
8540
8677
  };
8541
8678
 
8679
+ var __column_header_cells = function (header) {
8680
+ var out = [];
8681
+
8682
+ for (var i=0 ; i<header.length ; i++) {
8683
+ for (var j=0 ; j<header[i].length ; j++) {
8684
+ var cell = header[i][j].cell;
8685
+
8686
+ if (!out.includes(cell)) {
8687
+ out.push(cell);
8688
+ }
8689
+ }
8690
+ }
8691
+
8692
+ return out;
8693
+ }
8694
+
8542
8695
  var __column_selector = function ( settings, selector, opts )
8543
8696
  {
8544
8697
  var
8545
8698
  columns = settings.aoColumns,
8546
- names = _pluck( columns, 'sName' ),
8547
- titles = _pluck( columns, 'sTitle' ),
8548
- cells = DataTable.util.get('[].[].cell')(settings.aoHeader),
8549
- nodes = _unique( _flatten([], cells) );
8699
+ names, titles,
8700
+ nodes = __column_header_cells(settings.aoHeader);
8550
8701
 
8551
8702
  var run = function ( s ) {
8552
8703
  var selInt = _intVal( s );
@@ -8618,12 +8769,21 @@ var __column_selector = function ( settings, selector, opts )
8618
8769
  } );
8619
8770
 
8620
8771
  case 'name':
8772
+ // Don't get names, unless needed, and only get once if it is
8773
+ if (!names) {
8774
+ names = _pluck( columns, 'sName' );
8775
+ }
8776
+
8621
8777
  // match by name. `names` is column index complete and in order
8622
8778
  return names.map( function (name, i) {
8623
8779
  return name === match[1] ? i : null;
8624
8780
  } );
8625
8781
 
8626
8782
  case 'title':
8783
+ if (!titles) {
8784
+ titles = _pluck( columns, 'sTitle' );
8785
+ }
8786
+
8627
8787
  // match by column title
8628
8788
  return titles.map( function (title, i) {
8629
8789
  return title === match[1] ? i : null;
@@ -8662,7 +8822,11 @@ var __column_selector = function ( settings, selector, opts )
8662
8822
  [];
8663
8823
  };
8664
8824
 
8665
- return _selector_run( 'column', selector, run, settings, opts );
8825
+ var selected = _selector_run( 'column', selector, run, settings, opts );
8826
+
8827
+ return opts.columnOrder && opts.columnOrder === 'index'
8828
+ ? selected.sort(function (a, b) { return a - b; })
8829
+ : selected; // implied
8666
8830
  };
8667
8831
 
8668
8832
 
@@ -8786,6 +8950,12 @@ _api_registerPlural( 'columns().init()', 'column().init()', function () {
8786
8950
  }, 1 );
8787
8951
  } );
8788
8952
 
8953
+ _api_registerPlural( 'columns().names()', 'column().name()', function () {
8954
+ return this.iterator( 'column', function ( settings, column ) {
8955
+ return settings.aoColumns[column].sName;
8956
+ }, 1 );
8957
+ } );
8958
+
8789
8959
  _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
8790
8960
  return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
8791
8961
  return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
@@ -9212,7 +9382,10 @@ _api_register( 'order()', function ( order, dir ) {
9212
9382
  // otherwise a 2D array was passed in
9213
9383
 
9214
9384
  return this.iterator( 'table', function ( settings ) {
9215
- settings.aaSorting = Array.isArray(order) ? order.slice() : order;
9385
+ var resolved = [];
9386
+ _fnSortResolve(settings, resolved, order);
9387
+
9388
+ settings.aaSorting = resolved;
9216
9389
  } );
9217
9390
  } );
9218
9391
 
@@ -9338,7 +9511,7 @@ _api_register( 'search.fixed()', function ( name, search ) {
9338
9511
  var fixed = settings.searchFixed;
9339
9512
 
9340
9513
  if (! name) {
9341
- return Object.keys(fixed)
9514
+ return Object.keys(fixed);
9342
9515
  }
9343
9516
  else if (search === undefined) {
9344
9517
  return fixed[name];
@@ -9405,10 +9578,10 @@ _api_register([
9405
9578
  var fixed = settings.aoColumns[colIdx].searchFixed;
9406
9579
 
9407
9580
  if (! name) {
9408
- return Object.keys(fixed)
9581
+ return Object.keys(fixed);
9409
9582
  }
9410
9583
  else if (search === undefined) {
9411
- return fixed[name];
9584
+ return fixed[name] || null;
9412
9585
  }
9413
9586
  else if (search === null) {
9414
9587
  delete fixed[name];
@@ -9797,12 +9970,14 @@ _api_register( 'ready()', function ( fn ) {
9797
9970
  // Function to run either once the table becomes ready or
9798
9971
  // immediately if it is already ready.
9799
9972
  return this.tables().every(function () {
9973
+ var api = this;
9974
+
9800
9975
  if (this.context[0]._bInitComplete) {
9801
- fn.call(this);
9976
+ fn.call(api);
9802
9977
  }
9803
9978
  else {
9804
9979
  this.on('init.dt.DT', function () {
9805
- fn.call(this);
9980
+ fn.call(api);
9806
9981
  });
9807
9982
  }
9808
9983
  } );
@@ -9858,20 +10033,31 @@ _api_register( 'destroy()', function ( remove ) {
9858
10033
  jqTable.append( tfoot );
9859
10034
  }
9860
10035
 
10036
+ // Clean up the header / footer
10037
+ cleanHeader(thead, 'header');
10038
+ cleanHeader(tfoot, 'footer');
9861
10039
  settings.colgroup.remove();
9862
10040
 
9863
10041
  settings.aaSorting = [];
9864
10042
  settings.aaSortingFixed = [];
9865
10043
  _fnSortingClasses( settings );
9866
10044
 
10045
+ $(jqTable).find('th, td').removeClass(
10046
+ $.map(DataTable.ext.type.className, function (v) {
10047
+ return v;
10048
+ }).join(' ')
10049
+ );
10050
+
9867
10051
  $('th, td', thead)
9868
10052
  .removeClass(
10053
+ orderClasses.none + ' ' +
9869
10054
  orderClasses.canAsc + ' ' +
9870
10055
  orderClasses.canDesc + ' ' +
9871
10056
  orderClasses.isAsc + ' ' +
9872
10057
  orderClasses.isDesc
9873
10058
  )
9874
- .css('width', '');
10059
+ .css('width', '')
10060
+ .removeAttr('aria-sort');
9875
10061
 
9876
10062
  // Add the TR elements back into the table in their original order
9877
10063
  jqTbody.children().detach();
@@ -9951,6 +10137,19 @@ _api_register( 'i18n()', function ( token, def, plural ) {
9951
10137
  : resolved;
9952
10138
  } );
9953
10139
 
10140
+ // Needed for header and footer, so pulled into its own function
10141
+ function cleanHeader(node, className) {
10142
+ $(node).find('span.dt-column-order').remove();
10143
+ $(node).find('span.dt-column-title').each(function () {
10144
+ var title = $(this).html();
10145
+ $(this).parent().parent().append(title);
10146
+ $(this).remove();
10147
+ });
10148
+ $(node).find('div.dt-column-' + className).remove();
10149
+
10150
+ $('th, td', node).removeAttr('data-dt-column');
10151
+ }
10152
+
9954
10153
  /**
9955
10154
  * Version string for plug-ins to check compatibility. Allowed format is
9956
10155
  * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
@@ -9959,7 +10158,7 @@ _api_register( 'i18n()', function ( token, def, plural ) {
9959
10158
  * @type string
9960
10159
  * @default Version number
9961
10160
  */
9962
- DataTable.version = "2.2.1";
10161
+ DataTable.version = "2.3.0";
9963
10162
 
9964
10163
  /**
9965
10164
  * Private data store, containing all of the settings objects that are
@@ -10566,6 +10765,10 @@ DataTable.defaults = {
10566
10765
  "bSortCellsTop": null,
10567
10766
 
10568
10767
 
10768
+ /** Specify which row is the title row in the header. Replacement for bSortCellsTop */
10769
+ titleRow: null,
10770
+
10771
+
10569
10772
  /**
10570
10773
  * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
10571
10774
  * `sorting\_3` to the columns which are currently being sorted on. This is
@@ -10843,6 +11046,13 @@ DataTable.defaults = {
10843
11046
  1: "entry"
10844
11047
  },
10845
11048
 
11049
+ /**
11050
+ * Page length options
11051
+ */
11052
+ lengthLabels: {
11053
+ '-1': 'All'
11054
+ },
11055
+
10846
11056
  /**
10847
11057
  * This string is shown in preference to `zeroRecords` when the table is
10848
11058
  * empty of data (regardless of filtering). Note that this is an optional
@@ -11113,7 +11323,10 @@ DataTable.defaults = {
11113
11323
  /**
11114
11324
  * For server-side processing - use the data from the DOM for the first draw
11115
11325
  */
11116
- iDeferLoading: null
11326
+ iDeferLoading: null,
11327
+
11328
+ /** Event listeners */
11329
+ on: null
11117
11330
  };
11118
11331
 
11119
11332
  _fnHungarianMap( DataTable.defaults );
@@ -11940,10 +12153,7 @@ DataTable.models.oSettings = {
11940
12153
 
11941
12154
  /**
11942
12155
  * Indicate that if multiple rows are in the header and there is more than
11943
- * one unique cell per column, if the top one (true) or bottom one (false)
11944
- * should be used for sorting / title by DataTables.
11945
- * Note that this parameter will be set by the initialisation routine. To
11946
- * set a default use {@link DataTable.defaults}.
12156
+ * one unique cell per column. Replaced by titleRow
11947
12157
  */
11948
12158
  "bSortCellsTop": null,
11949
12159
 
@@ -12068,7 +12278,19 @@ DataTable.models.oSettings = {
12068
12278
  resizeObserver: null,
12069
12279
 
12070
12280
  /** Keep a record of the last size of the container, so we can skip duplicates */
12071
- containerWidth: -1
12281
+ containerWidth: -1,
12282
+
12283
+ /** Reverse the initial order of the data set on desc ordering */
12284
+ orderDescReverse: null,
12285
+
12286
+ /** Show / hide ordering indicators in headers */
12287
+ orderIndicators: true,
12288
+
12289
+ /** Default ordering listener */
12290
+ orderHandler: true,
12291
+
12292
+ /** Title row indicator */
12293
+ titleRow: null
12072
12294
  };
12073
12295
 
12074
12296
  /**
@@ -12898,7 +13120,7 @@ $.extend( true, DataTable.ext.renderer, {
12898
13120
  cell.addClass(classes.order.none);
12899
13121
  }
12900
13122
 
12901
- var legacyTop = settings.bSortCellsTop;
13123
+ var titleRow = settings.titleRow;
12902
13124
  var headerRows = cell.closest('thead').find('tr');
12903
13125
  var rowIdx = cell.parent().index();
12904
13126
 
@@ -12908,11 +13130,10 @@ $.extend( true, DataTable.ext.renderer, {
12908
13130
  cell.attr('data-dt-order') === 'disable' ||
12909
13131
  cell.parent().attr('data-dt-order') === 'disable' ||
12910
13132
 
12911
- // Legacy support for `orderCellsTop`. If it is set, then cells
12912
- // which are not in the top or bottom row of the header (depending
12913
- // on the value) do not get the sorting classes applied to them
12914
- (legacyTop === true && rowIdx !== 0) ||
12915
- (legacyTop === false && rowIdx !== headerRows.length - 1)
13133
+ // titleRow support, for defining a specific row in the header
13134
+ (titleRow === true && rowIdx !== 0) ||
13135
+ (titleRow === false && rowIdx !== headerRows.length - 1) ||
13136
+ (typeof titleRow === 'number' && rowIdx !== titleRow)
12916
13137
  ) {
12917
13138
  return;
12918
13139
  }
@@ -12922,7 +13143,7 @@ $.extend( true, DataTable.ext.renderer, {
12922
13143
  // `DT` namespace will allow the event to be removed automatically
12923
13144
  // on destroy, while the `dt` namespaced event is the one we are
12924
13145
  // listening for
12925
- $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx ) {
13146
+ $(settings.nTable).on( 'order.dt.DT column-visibility.dt.DT', function ( e, ctx, column ) {
12926
13147
  if ( settings !== ctx ) { // need to check this this is the host
12927
13148
  return; // table, not a nested one
12928
13149
  }
@@ -12933,6 +13154,16 @@ $.extend( true, DataTable.ext.renderer, {
12933
13154
  return;
12934
13155
  }
12935
13156
 
13157
+ var orderedColumns = _pluck(sorting, 'col');
13158
+
13159
+ // This handler is only needed on column visibility if the column is part of the
13160
+ // ordering. If it isn't, then we can bail out to save performance. It could be a
13161
+ // separate event handler, but this is a balance between code reuse / size and performance
13162
+ // console.log(e, e.name, column, orderedColumns, orderedColumns.includes(column))
13163
+ if (e.type === 'column-visibility' && ! orderedColumns.includes(column)) {
13164
+ return;
13165
+ }
13166
+
12936
13167
  var i;
12937
13168
  var orderClasses = classes.order;
12938
13169
  var columns = ctx.api.columns( cell );
@@ -12941,8 +13172,8 @@ $.extend( true, DataTable.ext.renderer, {
12941
13172
  var ariaType = '';
12942
13173
  var indexes = columns.indexes();
12943
13174
  var sortDirs = columns.orderable(true).flatten();
12944
- var orderedColumns = _pluck(sorting, 'col');
12945
13175
  var tabIndex = settings.iTabIndex;
13176
+ var canOrder = ctx.orderHandler && orderable;
12946
13177
 
12947
13178
  cell
12948
13179
  .removeClass(
@@ -12950,8 +13181,8 @@ $.extend( true, DataTable.ext.renderer, {
12950
13181
  orderClasses.isDesc
12951
13182
  )
12952
13183
  .toggleClass( orderClasses.none, ! orderable )
12953
- .toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
12954
- .toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
13184
+ .toggleClass( orderClasses.canAsc, canOrder && sortDirs.includes('asc') )
13185
+ .toggleClass( orderClasses.canDesc, canOrder && sortDirs.includes('desc') );
12955
13186
 
12956
13187
  // Determine if all of the columns that this cell covers are included in the
12957
13188
  // current ordering
@@ -13000,16 +13231,16 @@ $.extend( true, DataTable.ext.renderer, {
13000
13231
  cell.removeAttr('aria-sort');
13001
13232
  }
13002
13233
 
13003
- cell.attr('aria-label', orderable
13004
- ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
13005
- : col.ariaTitle
13006
- );
13007
-
13008
13234
  // Make the headers tab-able for keyboard navigation
13009
13235
  if (orderable) {
13010
13236
  var orderSpan = cell.find('.dt-column-order');
13011
13237
 
13012
- orderSpan.attr('role', 'button');
13238
+ orderSpan
13239
+ .attr('role', 'button')
13240
+ .attr('aria-label', orderable
13241
+ ? col.ariaTitle + ctx.api.i18n('oAria.orderable' + ariaType)
13242
+ : col.ariaTitle
13243
+ );
13013
13244
 
13014
13245
  if (tabIndex !== -1) {
13015
13246
  orderSpan.attr('tabindex', tabIndex);
@@ -13710,12 +13941,17 @@ DataTable.feature.register( 'pageLength', function ( settings, opts ) {
13710
13941
  } );
13711
13942
 
13712
13943
  for ( i=0 ; i<lengths.length ; i++ ) {
13713
- select[0][ i ] = new Option(
13714
- typeof language[i] === 'number' ?
13944
+ // Attempt to look up the length from the i18n options
13945
+ var label = settings.api.i18n('lengthLabels.' + lengths[i], null);
13946
+
13947
+ if (label === null) {
13948
+ // If not present, fallback to old style
13949
+ label = typeof language[i] === 'number' ?
13715
13950
  settings.fnFormatNumber( language[i] ) :
13716
- language[i],
13717
- lengths[i]
13718
- );
13951
+ language[i];
13952
+ }
13953
+
13954
+ select[0][ i ] = new Option(label, lengths[i]);
13719
13955
  }
13720
13956
 
13721
13957
  // add for and id to label and input