datatables.net 3.0.0-beta.1 → 3.0.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.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! DataTables 3.0.0-beta.1
1
+ /*! DataTables
2
2
  * Copyright (c) SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
@@ -80,9 +80,11 @@ const reRegexCharacters = new RegExp('(\\' +
80
80
  // implementations differ between browsers.
81
81
  const reDate = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/;
82
82
  const reNewLines = /[\r\n\u2028]/g;
83
+ const isoTimezone = /[T\s]\d{2}.*?(Z|[+-]\d{2}(?::?\d{2})?)$/;
83
84
 
84
85
  var regex = /*#__PURE__*/Object.freeze({
85
86
  __proto__: null,
87
+ isoTimezone: isoTimezone,
86
88
  reDate: reDate,
87
89
  reFormattedNumeric: reFormattedNumeric,
88
90
  reHtml: reHtml,
@@ -554,17 +556,29 @@ function ajax(optionsIn) {
554
556
  if (options.contentType && !(options.data instanceof FormData)) {
555
557
  xhr.setRequestHeader('Content-Type', options.contentType);
556
558
  }
559
+ // Add a X-Request-With header, as jQuery does so and some server-side
560
+ // platforms look for it. Only for same domain though.
561
+ if (options.headers &&
562
+ !options.headers['X-Requested-With'] &&
563
+ !isCrossDomain(options.url)) {
564
+ options.headers['X-Requested-With'] = 'XMLHttpRequest';
565
+ }
557
566
  each(options.headers, (key, val) => {
558
567
  xhr.setRequestHeader(key, val);
559
568
  });
560
569
  if (options.data instanceof FormData) {
561
570
  sendData = options.data;
562
571
  }
563
- else if (method !== 'GET' && options.data && typeof options.data !== 'string') {
564
- sendData = serialize(options.data, options.traditional);
565
- sendData = convertSpaces(sendData, options);
566
- // So beforeSend matches how jQuery behaves
567
- options.data = sendData;
572
+ else if (method !== 'GET' && options.data) {
573
+ if (typeof options.data === 'string') {
574
+ sendData = options.data;
575
+ }
576
+ else {
577
+ sendData = serialize(options.data, options.traditional);
578
+ sendData = convertSpaces(sendData, options);
579
+ // So beforeSend matches how jQuery behaves
580
+ options.data = sendData;
581
+ }
568
582
  }
569
583
  xhr.onreadystatechange = function () {
570
584
  if (xhr.readyState != 4) {
@@ -614,6 +628,7 @@ function ajax(optionsIn) {
614
628
  if (options.beforeSend) {
615
629
  if (options.beforeSend.call(options, xhr, options) === false) {
616
630
  xhr.abort();
631
+ return xhr;
617
632
  }
618
633
  }
619
634
  xhr.send(sendData);
@@ -651,6 +666,17 @@ function convertSpaces(sendData, options) {
651
666
  ? sendData.replace(/%20/g, '+')
652
667
  : sendData;
653
668
  }
669
+ /**
670
+ * Determine if a url is a cross domain request or not
671
+ *
672
+ * @param url URL to check
673
+ * @returns True if cross domain, false otherwise
674
+ */
675
+ function isCrossDomain(url) {
676
+ // Use the current page as the base to handle relative URLs correctly
677
+ const target = new URL(url, window.location.origin);
678
+ return target.origin !== window.location.origin;
679
+ }
654
680
  /**
655
681
  * Get the HTTP method from the Ajax request options
656
682
  *
@@ -789,7 +815,7 @@ function allUnique(src) {
789
815
  * @returns Flattened array
790
816
  */
791
817
  function flatten(out, val) {
792
- if (Array.isArray(val)) {
818
+ if (Array.isArray(val) || arrayLike(val)) {
793
819
  for (var i = 0; i < val.length; i++) {
794
820
  flatten(out, val[i]);
795
821
  }
@@ -1332,7 +1358,7 @@ var timer = /*#__PURE__*/Object.freeze({
1332
1358
  * @returns true if this version of DataTables is greater or equal to the
1333
1359
  * required version, or false if this version of DataTales is not suitable
1334
1360
  */
1335
- function check(version1, version2) {
1361
+ function check$1(version1, version2) {
1336
1362
  let dt = external('datatable');
1337
1363
  var parts1 = version2 ? version2.split('.') : dt.ext.version.split('.');
1338
1364
  var parts2 = version1.split('.');
@@ -1352,7 +1378,7 @@ function check(version1, version2) {
1352
1378
 
1353
1379
  var version = /*#__PURE__*/Object.freeze({
1354
1380
  __proto__: null,
1355
- check: check
1381
+ check: check$1
1356
1382
  });
1357
1383
 
1358
1384
  // Note that the aliased properties are for compatibility with DataTables 2-
@@ -1570,9 +1596,12 @@ function parseEventName(original) {
1570
1596
  isFocus = true;
1571
1597
  }
1572
1598
  else if (name === 'blur') {
1573
- name = 'blurout';
1599
+ name = 'focusout';
1574
1600
  isFocus = true;
1575
1601
  }
1602
+ else if (name === 'ready') {
1603
+ name = 'DOMContentLoaded';
1604
+ }
1576
1605
  return {
1577
1606
  eventName: name,
1578
1607
  isFocus,
@@ -1609,6 +1638,14 @@ function add(el, nameFull, handler, selector, one) {
1609
1638
  if (!eventName) {
1610
1639
  return;
1611
1640
  }
1641
+ // Special handling for the "ready" event - it will trigger when the content
1642
+ // is ready, but also if it is already ready, when added.
1643
+ if (el === document && eventName === 'DOMContentLoaded' && nameFull.includes('ready')) {
1644
+ if (document.readyState === 'complete') {
1645
+ handler(new Event('DOMContentLoaded'));
1646
+ return;
1647
+ }
1648
+ }
1612
1649
  // Create a function that will be the actual event handler, and performs any
1613
1650
  // logic we need, such as delegate handling and adding properties.
1614
1651
  let wrapped = function (event) {
@@ -1634,6 +1671,11 @@ function add(el, nameFull, handler, selector, one) {
1634
1671
  if (!dTarget) {
1635
1672
  return;
1636
1673
  }
1674
+ if (isHover &&
1675
+ event.relatedTarget &&
1676
+ dTarget.contains(event.relatedTarget)) {
1677
+ return;
1678
+ }
1637
1679
  callScope = dTarget;
1638
1680
  }
1639
1681
  // Set the properties that jQuery adds to the event object
@@ -1691,7 +1733,7 @@ function remove(el, nameFull, handler, selector) {
1691
1733
  wrapped.delegateSelector === selector &&
1692
1734
  wrapped.original === handler);
1693
1735
  }
1694
- if (eventName && selector) {
1736
+ else if (eventName && selector) {
1695
1737
  removeEvents = stored.filter(wrapped => wrapped.type === eventName &&
1696
1738
  wrapped.delegateSelector === selector);
1697
1739
  }
@@ -1906,8 +1948,7 @@ class Dom {
1906
1948
  }
1907
1949
  }
1908
1950
  if (sort) {
1909
- Array.prototype.sort.call(this, documentOrder);
1910
- // this.sort(documentOrder);
1951
+ this.sort();
1911
1952
  }
1912
1953
  return this;
1913
1954
  }
@@ -1923,29 +1964,33 @@ class Dom {
1923
1964
  if (!content) {
1924
1965
  return this;
1925
1966
  }
1926
- if (Array.isArray(content)) {
1927
- content.forEach(c => this.append(c));
1928
- return this;
1967
+ if (!arrayLike(content)) {
1968
+ content = [content];
1929
1969
  }
1930
- return this.each(el => {
1931
- if (content instanceof Dom) {
1932
- content.each(item => {
1933
- el.append(item);
1934
- });
1935
- }
1936
- else if (typeof content === 'string') {
1937
- el.insertAdjacentHTML('beforeend', content);
1938
- }
1939
- else if (arrayLike(content)) {
1940
- // Allow for a jQuery object being passed
1941
- let arrayLike = content;
1942
- for (let i = 0; i < arrayLike.length; i++) {
1943
- el.append(arrayLike[i]);
1970
+ // Generate a flat array of the content to be added, with nulls removed
1971
+ // this means it will be an array of nodes and / or strings
1972
+ let flatContent = flatten([], content).filter(c => !!c);
1973
+ /// Got a string somewhere in it, so need to use insertAdjacentHTML
1974
+ if (flatContent.find(val => typeof val === 'string')) {
1975
+ return this.each(el => {
1976
+ for (let i = 0; i < flatContent.length; i++) {
1977
+ if (typeof flatContent[i] === 'string') {
1978
+ el.insertAdjacentHTML('beforeend', flatContent[i]);
1979
+ }
1980
+ else {
1981
+ el.append(flatContent[i]);
1982
+ }
1944
1983
  }
1984
+ });
1985
+ }
1986
+ // Otherwise we can use a document fragment for a single mutation
1987
+ // on the main document
1988
+ return this.each(el => {
1989
+ let fragment = new DocumentFragment();
1990
+ for (let i = 0; i < flatContent.length; i++) {
1991
+ fragment.append(flatContent[i]);
1945
1992
  }
1946
- else {
1947
- el.append(content);
1948
- }
1993
+ el.append(fragment);
1949
1994
  });
1950
1995
  }
1951
1996
  /**
@@ -2112,9 +2157,7 @@ class Dom {
2112
2157
  css(rule, value) {
2113
2158
  // String getter
2114
2159
  if (typeof rule === 'string' && value === undefined) {
2115
- return this.length
2116
- ? getComputedStyle(this[0])[rule]
2117
- : null;
2160
+ return this.length ? getComputedStyle(this[0])[rule] : null;
2118
2161
  }
2119
2162
  return this.each(el => {
2120
2163
  if (typeof rule === 'string') {
@@ -2142,11 +2185,11 @@ class Dom {
2142
2185
  return this.length ? dataConvert(this[0].dataset[name]) : null;
2143
2186
  }
2144
2187
  if (typeof name === 'string') {
2145
- this.each(el => el.dataset[name] = JSON.stringify(value));
2188
+ this.each(el => (el.dataset[name] = JSON.stringify(value)));
2146
2189
  }
2147
2190
  else {
2148
2191
  each(name, (key, val) => {
2149
- this.each(el => el.dataset[key] = JSON.stringify(val));
2192
+ this.each(el => (el.dataset[key] = JSON.stringify(val)));
2150
2193
  });
2151
2194
  }
2152
2195
  return this;
@@ -2698,6 +2741,18 @@ class Dom {
2698
2741
  el.style.display = 'block';
2699
2742
  });
2700
2743
  }
2744
+ /**
2745
+ * Sort the DOM elements into document order.
2746
+ *
2747
+ * This is normally not needed as elements selected with a DOM selector are
2748
+ * automatically sorted in document order. However, in the case of elements
2749
+ * being added as an array, their order will be retained. In such as case
2750
+ * you might wish to sort them in document order.
2751
+ */
2752
+ sort() {
2753
+ Array.prototype.sort.call(this, documentOrder);
2754
+ return this;
2755
+ }
2701
2756
  text(txt) {
2702
2757
  if (txt === undefined) {
2703
2758
  return this.count() ? this[0].textContent : null;
@@ -3858,7 +3913,7 @@ const ext = {
3858
3913
  * Software version
3859
3914
  * @type string
3860
3915
  */
3861
- version: '3.0.0-beta.1'
3916
+ version: '3.0.0'
3862
3917
  };
3863
3918
  //
3864
3919
  // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
@@ -3873,7 +3928,7 @@ Object.assign(ext, {
3873
3928
  oStdClasses: ext.classes,
3874
3929
  oPagination: ext.pager,
3875
3930
  sVersion: ext.version,
3876
- fnVersionCheck: check
3931
+ fnVersionCheck: check$1
3877
3932
  });
3878
3933
 
3879
3934
  /**
@@ -4102,7 +4157,7 @@ function arrayApply(arr, data) {
4102
4157
  function listener(that, name, src) {
4103
4158
  let srcArr = Array.isArray(src) ? src : [src];
4104
4159
  for (var i = 0; i < srcArr.length; i++) {
4105
- that.on(name + '.dt', srcArr[i]);
4160
+ that.on(name + '.dt.DT', srcArr[i]);
4106
4161
  }
4107
4162
  }
4108
4163
  /**
@@ -4151,7 +4206,7 @@ function __mldObj(d, format, locale) {
4151
4206
  var dt;
4152
4207
  resolveWindowLibs();
4153
4208
  if (__moment) {
4154
- dt = __moment.utc(d, format, locale, true);
4209
+ dt = __moment(d, format, locale, true);
4155
4210
  if (!dt.isValid()) {
4156
4211
  return null;
4157
4212
  }
@@ -4217,10 +4272,12 @@ function __mlHelper(localeString) {
4217
4272
  // gives milliseconds epoch
4218
4273
  return d.valueOf();
4219
4274
  }
4220
- },
4221
- className: 'dt-right'
4275
+ }
4222
4276
  });
4223
4277
  }
4278
+ if (!store.className[typeName]) {
4279
+ store.className[typeName] = 'dt-right';
4280
+ }
4224
4281
  return function (d, type) {
4225
4282
  // Allow for a default value
4226
4283
  if (d === null || d === undefined) {
@@ -4254,6 +4311,15 @@ function __mlHelper(localeString) {
4254
4311
  !(d instanceof Date)) {
4255
4312
  return d;
4256
4313
  }
4314
+ // Determine if there is a timezone. If there is, we want to reuse
4315
+ // it for the output, so the timezone doesn't change between the
4316
+ // input and output.
4317
+ let options = {};
4318
+ let tzMatch = typeof d === 'string' ? d.match(util.regex.isoTimezone) : null;
4319
+ if (tzMatch) {
4320
+ options.timeZone = tzMatch[1] === 'Z' ? 'UTC' : tzMatch[1];
4321
+ }
4322
+ // Get a Date object (Luxon, moment or Date)
4257
4323
  var dt = __mldObj(d, from, locale);
4258
4324
  if (dt === null) {
4259
4325
  return d;
@@ -4262,7 +4328,7 @@ function __mlHelper(localeString) {
4262
4328
  return dt;
4263
4329
  }
4264
4330
  var formatted = to === null
4265
- ? __mld(dt, 'toDate', 'toJSDate', '')[localeString](navigator.language, { timeZone: 'UTC' })
4331
+ ? __mld(dt, 'toDate', 'toJSDate', '')[localeString](navigator.language, options)
4266
4332
  : __mld(dt, 'format', 'toFormat', 'toISOString', to);
4267
4333
  // XSS protection
4268
4334
  return type === 'display' ? util.escapeHtml(formatted) : formatted;
@@ -4312,10 +4378,12 @@ function datetime(format, locale) {
4312
4378
  pre: function (d) {
4313
4379
  return __mldObj(d, format, locale) || 0;
4314
4380
  }
4315
- },
4316
- className: 'dt-right'
4381
+ }
4317
4382
  });
4318
4383
  }
4384
+ if (!store.className[typeName]) {
4385
+ store.className[typeName] = 'dt-right';
4386
+ }
4319
4387
  }
4320
4388
  /**
4321
4389
  * Helpers for `columns.render`.
@@ -4984,6 +5052,7 @@ function invalidate(settings, rowIdx, src, colIdx) {
4984
5052
  // Update DataTables special `DT_*` attributes for the row
4985
5053
  rowAttributes(settings, row);
4986
5054
  }
5055
+ callbackFire(settings, null, 'rowInvalidate', [settings, rowIdx, colIdx], false);
4987
5056
  }
4988
5057
  /**
4989
5058
  * Get the cells and data for a given row - from a <tr> element
@@ -5329,9 +5398,9 @@ function getWideStrings(settings, colIdx) {
5329
5398
  // Don't want script, dialog or template tags in the width
5330
5399
  // calculations as they are hidden content
5331
5400
  cellString = cellString
5332
- .replace(/<script[\s\S]*?<\/script>/gi, ' ')
5333
- .replace(/<dialog[\s\S]*?<\/dialog>/gi, ' ')
5334
- .replace(/<template[\s\S]*?<\/template>/gi, ' ');
5401
+ .replace(/<script[\s\S]*?<\/script(?:\s[^>]*)?>/gi, ' ')
5402
+ .replace(/<dialog[\s\S]*?<\/dialog(?:\s[^>]*)?>/gi, ' ')
5403
+ .replace(/<template[\s\S]*?<\/template(?:\s[^>]*)?>/gi, ' ');
5335
5404
  var noHtml = util.string
5336
5405
  .stripHtml(cellString, ' ')
5337
5406
  .replace(/&nbsp;/g, ' ');
@@ -5418,8 +5487,8 @@ function featureTable(settings) {
5418
5487
  let captionSide = caption
5419
5488
  ? caption._captionSide
5420
5489
  : null;
5421
- let headerClone = table.clone(false);
5422
- let footerClone = table.clone(false);
5490
+ let tableCloneHeader = table.clone(false);
5491
+ let tableCloneFooter = table.clone(false);
5423
5492
  let footer = table.children('tfoot');
5424
5493
  let size = function (s) {
5425
5494
  return !s ? '100%' : stringToCss(s);
@@ -5440,11 +5509,10 @@ function featureTable(settings) {
5440
5509
  * table - scroll foot table
5441
5510
  * tfoot - tfoot
5442
5511
  */
5443
- let scroller = Dom
5444
- .c('div')
5512
+ let scroller = Dom.c('div')
5445
5513
  .classAdd(classes.container)
5446
- .append(Dom
5447
- .c('div')
5514
+ .attr('role', 'table')
5515
+ .append(Dom.c('div')
5448
5516
  .classAdd(classes.header.self)
5449
5517
  .css({
5450
5518
  overflow: 'hidden',
@@ -5452,40 +5520,41 @@ function featureTable(settings) {
5452
5520
  border: '0',
5453
5521
  width: scrollX ? size(scrollX) : '100%'
5454
5522
  })
5455
- .append(Dom
5456
- .c('div')
5523
+ .attr('role', 'none')
5524
+ .append(Dom.c('div')
5457
5525
  .classAdd(classes.header.inner)
5458
5526
  .css({
5459
5527
  'box-sizing': 'content-box',
5460
5528
  width: scroll.xInner || '100%'
5461
5529
  })
5462
- .append(headerClone
5530
+ .attr('role', 'none')
5531
+ .append(tableCloneHeader
5463
5532
  .attrRemove('id')
5464
5533
  .css('margin-left', '0')
5465
5534
  .append(captionSide === 'top' ? caption : null)
5466
5535
  .append(table.children('thead')))))
5467
- .append(Dom
5468
- .c('div')
5536
+ .append(Dom.c('div')
5469
5537
  .classAdd(classes.body)
5470
5538
  .css({
5471
5539
  position: 'relative',
5472
5540
  overflow: 'auto',
5473
5541
  width: size(scrollX)
5474
5542
  })
5543
+ .attr('role', 'none')
5475
5544
  .append(table));
5476
5545
  if (footer.count()) {
5477
- scroller.append(Dom
5478
- .c('div')
5546
+ scroller.append(Dom.c('div')
5479
5547
  .classAdd(classes.footer.self)
5480
5548
  .css({
5481
5549
  overflow: 'hidden',
5482
5550
  border: '0',
5483
5551
  width: scrollX ? size(scrollX) : '100%'
5484
5552
  })
5485
- .append(Dom
5486
- .c('div')
5553
+ .attr('role', 'none')
5554
+ .append(Dom.c('div')
5487
5555
  .classAdd(classes.footer.inner)
5488
- .append(footerClone
5556
+ .attr('role', 'none')
5557
+ .append(tableCloneFooter
5489
5558
  .attrRemove('id')
5490
5559
  .css('margin-left', '0')
5491
5560
  .append(captionSide === 'bottom' ? caption : null)
@@ -5523,6 +5592,22 @@ function featureTable(settings) {
5523
5592
  settings.scrollFoot = scrollFoot;
5524
5593
  // On redraw - align columns
5525
5594
  settings.callbacks.draw.push(scrollDraw);
5595
+ // Aria roles - because we break the table up into parts we need to be very
5596
+ // explicit with the roles to create the accessability tree for the table,
5597
+ // otherwise browser's attempt to "fix" the tree by filling in what it
5598
+ // thinks are gaps. The static elements that we can assign roles to are done
5599
+ // here. Dynamic ones are done in the draw function below.
5600
+ table.attr('role', 'none');
5601
+ table.find('tbody').attr('role', 'rowgroup');
5602
+ tableCloneHeader.attr('role', 'none');
5603
+ tableCloneFooter.attr('role', 'none');
5604
+ settings.colgroup.find('colgroup').attr('role', 'none');
5605
+ // Move the info feature's aria desc by to the new "table"
5606
+ let describedBy = table.attr('aria-describedby');
5607
+ if (describedBy) {
5608
+ scroller.attr('aria-describedby', describedBy);
5609
+ table.attrRemove('aria-describedby');
5610
+ }
5526
5611
  return scroller.get(0);
5527
5612
  }
5528
5613
  /**
@@ -5555,10 +5640,13 @@ function scrollDraw(settings) {
5555
5640
  else {
5556
5641
  settings.scrollBarVis = scrollBarVis;
5557
5642
  }
5643
+ header.find('thead').attr('role', 'rowgroup');
5644
+ footer.find('tfoot').attr('role', 'rowgroup');
5558
5645
  // 1. Re-create the table inside the scrolling div
5559
5646
  // Remove the old minimised thead and tfoot elements in the inner table
5560
5647
  table.children('thead, tfoot').remove();
5561
- // Clone the current header and footer elements and then place it into the inner table
5648
+ // Clone the current header and footer elements and then place it into the
5649
+ // inner table
5562
5650
  headerCopy = header.clone(true).prependTo(table);
5563
5651
  headerCopy.find('th, td').attrRemove('tabindex');
5564
5652
  headerCopy.find('[id]').attrRemove('id');
@@ -5593,8 +5681,7 @@ function scrollDraw(settings) {
5593
5681
  }
5594
5682
  }
5595
5683
  if (firstTr) {
5596
- let colSizes = Dom
5597
- .s(firstTr)
5684
+ let colSizes = Dom.s(firstTr)
5598
5685
  .children('th, td')
5599
5686
  .mapTo(function (cell, idx) {
5600
5687
  return {
@@ -5659,6 +5746,18 @@ function scrollDraw(settings) {
5659
5746
  }
5660
5747
  // Correct DOM ordering for colgroup - comes before the thead
5661
5748
  table.children('colgroup').prependTo(table);
5749
+ // Remove tabindex from the hidden row elements
5750
+ table.find('thead, tfoot').find('[tabindex]').attrRemove('tabindex');
5751
+ // Dynamic ARIA roles - see setup for details on why this is needed
5752
+ table
5753
+ .find('thead, tfoot')
5754
+ .attr('role', 'none')
5755
+ .find('[role]')
5756
+ .attrRemove('role');
5757
+ table.find('tbody tr:not([role])').attr('role', 'row');
5758
+ table.find('tbody td:not([role]), tbody th:not([role])').attr('role', 'cell');
5759
+ scrollAria(headerCopy);
5760
+ scrollAria(footerCopy);
5662
5761
  // Adjust the position of the header in case we loose the y-scrollbar
5663
5762
  divBody.trigger('scroll');
5664
5763
  // If sorting or filtering has occurred, jump the scrolling back to the top
@@ -5667,6 +5766,18 @@ function scrollDraw(settings) {
5667
5766
  divBodyEl.scrollTop(0);
5668
5767
  }
5669
5768
  }
5769
+ /**
5770
+ * Apply ARIA roles for the header / footer of a scrolling table
5771
+ * @param element
5772
+ */
5773
+ function scrollAria(element) {
5774
+ if (element) {
5775
+ element.find('tfoot:not([role])').attr('role', 'rowgroup');
5776
+ element.find('tr:not([role])').attr('role', 'row');
5777
+ element.find('th:not([role])').attr('role', 'columnheader');
5778
+ element.find('td:not([role])').attr('role', 'cell');
5779
+ }
5780
+ }
5670
5781
 
5671
5782
  /**
5672
5783
  * Add a column to the list used for the table with default values
@@ -5890,6 +6001,7 @@ function columnTypes(settings) {
5890
6001
  var types = ext.type.detect;
5891
6002
  var i, iLen, j, jen, k, ken;
5892
6003
  var col, detectedType, cache;
6004
+ var originalTypes = columns.map(c => c.type).join(',');
5893
6005
  // For each column, spin over the data type detection functions, seeing if
5894
6006
  // one matches
5895
6007
  for (i = 0, iLen = columns.length; i < iLen; i++) {
@@ -5987,6 +6099,10 @@ function columnTypes(settings) {
5987
6099
  _columnAutoRender(settings, i);
5988
6100
  }
5989
6101
  }
6102
+ var newTypes = columns.map(c => c.type).join(',');
6103
+ if (newTypes !== originalTypes) {
6104
+ callbackFire(settings, null, 'columnTypes', [settings], false);
6105
+ }
5990
6106
  }
5991
6107
  /**
5992
6108
  * Apply an auto detected renderer to data which doesn't yet have a renderer
@@ -7188,13 +7304,13 @@ function implementState(settings, s, callback) {
7188
7304
  let idx = currentNames.indexOf(col[0]);
7189
7305
  if (idx < 0) {
7190
7306
  // If the column was not found ignore it and continue
7191
- return;
7307
+ continue;
7192
7308
  }
7193
7309
  set[0] = idx;
7194
7310
  }
7195
7311
  else if (set[0] >= columns.length) {
7196
7312
  // If the column index is out of bounds ignore it and continue
7197
- return;
7313
+ continue;
7198
7314
  }
7199
7315
  settings.order.push(set);
7200
7316
  }
@@ -8331,6 +8447,11 @@ function reDraw(settings, holdPosition, recompute) {
8331
8447
  if (holdPosition !== true) {
8332
8448
  settings.displayStart = 0;
8333
8449
  }
8450
+ else {
8451
+ // Keep position, but make sure that there is actually data to display,
8452
+ // otherwise we need to rewind a bit (e.g. if rows were deleted)
8453
+ lengthOverflow(settings);
8454
+ }
8334
8455
  // Let any modules know about the draw hold position state (used by
8335
8456
  // scrolling internally)
8336
8457
  settings.drawHold = holdPosition;
@@ -9507,12 +9628,13 @@ function selectCells(settings, selector, opts) {
9507
9628
  // Otherwise the selector is a node, and there is one last option - the
9508
9629
  // element might be a child of an element which has dt-row and dt-column
9509
9630
  // data attributes
9510
- host = Dom.s(s).closest('*[data-dt-row]');
9511
- return host.count()
9631
+ let rowHost = Dom.s(s).closest('*[data-dt-row]');
9632
+ let columnHost = Dom.s(s).closest('*[data-dt-column]');
9633
+ return rowHost.count()
9512
9634
  ? [
9513
9635
  {
9514
- row: host.data('dt-row'),
9515
- column: host.data('dt-column')
9636
+ row: parseInt(rowHost.attr('data-dt-row')),
9637
+ column: parseInt(columnHost.attr('data-dt-column'))
9516
9638
  }
9517
9639
  ]
9518
9640
  : [];
@@ -9825,7 +9947,7 @@ function selectColumns(settings, selector, opts) {
9825
9947
  // Otherwise a node which might have a `dt-column` data attribute, or be
9826
9948
  // a child or such an element
9827
9949
  var host = Dom.s(s).closest('*[data-dt-column]');
9828
- return host.count() ? [host.data('dt-column')] : [];
9950
+ return host.count() ? [parseInt(host.attr('data-dt-column'))] : [];
9829
9951
  };
9830
9952
  var selected = selectorRun('column', selector, run, settings, opts);
9831
9953
  return opts.columnOrder && opts.columnOrder === 'index'
@@ -10022,18 +10144,19 @@ registerPlural('columns().widths()', 'column().width()', function () {
10022
10144
  // Injects a fake row into the table for just a moment so the widths can
10023
10145
  // be read, regardless of colspan in the header and rows being present
10024
10146
  // in the body
10025
- var columns = this.columns(':visible').count();
10147
+ var columns = this.columns(':visible');
10026
10148
  var row = Dom
10027
10149
  .c('tr')
10028
- .html('<td>' + Array(columns).join('</td><td>') + '</td>');
10150
+ .html('<td>' + Array(columns.count()).join('</td><td>') + '</td>');
10029
10151
  Dom.s(this.table().body()).append(row);
10030
- var widths = row.children().mapTo(el => {
10031
- return Dom.s(el).width('outer');
10152
+ var widths = [];
10153
+ var indexes = columns.indexes();
10154
+ row.children().each((el, idx) => {
10155
+ widths[indexes[idx]] = Dom.s(el).width('outer');
10032
10156
  });
10033
10157
  row.remove();
10034
- return this.iterator('column', function (settings, column) {
10035
- var visIdx = columnIndexToVisible(settings, column);
10036
- return visIdx !== null ? widths[visIdx] : 0;
10158
+ return this.iterator('column', (settings, column) => {
10159
+ return widths[column] || 0;
10037
10160
  }, true);
10038
10161
  });
10039
10162
  registerPlural('columns().indexes()', 'column().index()', function (type) {
@@ -10505,7 +10628,7 @@ function selectRows(settings, selector, opts) {
10505
10628
  }
10506
10629
  else {
10507
10630
  var host = Dom.s(sel).closest('*[data-dt-row]');
10508
- return host.count() ? [host.data('dt-row')] : [];
10631
+ return host.count() ? [parseInt(host.attr('data-dt-row'))] : [];
10509
10632
  }
10510
10633
  }
10511
10634
  // ID selector. Want to always be able to select rows by id, regardless
@@ -10537,8 +10660,7 @@ function selectRows(settings, selector, opts) {
10537
10660
  // Get nodes in the order from the `rows` array with null values removed
10538
10661
  var nodes = util.array.removeEmpty(util.array.pluckOrder(settings.data, rows, 'tr'));
10539
10662
  // Selector - selector string, array of nodes or jQuery object.
10540
- return Dom
10541
- .s(nodes)
10663
+ return Dom.s(nodes)
10542
10664
  .filter(sel)
10543
10665
  .mapTo((el) => el._DT_RowIndex);
10544
10666
  };
@@ -10683,7 +10805,7 @@ register('row().data()', function (data) {
10683
10805
  util.set(ctx[0].rowId)(data, row.tr.id);
10684
10806
  }
10685
10807
  // Automatically invalidate
10686
- invalidate(ctx[0], this[0], 'data');
10808
+ invalidate(ctx[0], this[0][0], 'data');
10687
10809
  return this;
10688
10810
  });
10689
10811
  register('row().node()', function () {
@@ -11001,10 +11123,10 @@ register('caption()', function (value, side) {
11001
11123
  caption.css('caption-side', side);
11002
11124
  caption.get(0)._captionSide = side;
11003
11125
  }
11004
- if (container.find('div.dataTables_scroll').count()) {
11005
- var selector = side === 'top' ? 'Head' : 'Foot';
11126
+ if (container.find('div.dt-scroll').count()) {
11127
+ var selector = side === 'top' ? 'head' : 'foot';
11006
11128
  container
11007
- .find('div.dataTables_scroll' + selector + ' table')
11129
+ .find('div.dt-scroll-' + selector + ' table')
11008
11130
  .prepend(caption);
11009
11131
  }
11010
11132
  else {
@@ -11017,6 +11139,272 @@ register('caption.node()', function () {
11017
11139
  return ctx.length ? ctx[0].captionNode : null;
11018
11140
  });
11019
11141
 
11142
+ /**
11143
+ * What's this!? "DataTables Plus" is a commercial set of extensions for
11144
+ * DataTables, such as Editor, and the functions in this file allow a license
11145
+ * key to be provided (`DataTable.key(...)`) to unlock those features.
11146
+ *
11147
+ * This is the modal that I've selected to make DataTables sustainable, open
11148
+ * source core, with some commercial extensions available.
11149
+ *
11150
+ * Please support DataTables and open source by purchasing a Plus license from
11151
+ * https://datatables.net/plus .
11152
+ */
11153
+ let _ready = false;
11154
+ let _notice;
11155
+ let _processingKey = false;
11156
+ let _delayedReleaseDate = null;
11157
+ let _delayedSoftware = null;
11158
+ const _licenseInfo = {
11159
+ developers: 0,
11160
+ type: null,
11161
+ expires: null,
11162
+ valid: null
11163
+ };
11164
+ const _wm = Dom.c('div');
11165
+ const _publicKey = 'BE1A9w9D9U/4s4/TogY+1sW/dLJ8IquzK1PmV70J93ZTIvXMZ0eV2NAb52ntpgwVFySSB2fOI7geLNO737rQAyo=';
11166
+ /**
11167
+ * Convert a base64 string to a binary array
11168
+ *
11169
+ * @param b64 Source string
11170
+ * @returns Array
11171
+ */
11172
+ function b64ToBuf(b64) {
11173
+ return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
11174
+ }
11175
+ /**
11176
+ * Logic to check the trial and plus license expiry and display messages if
11177
+ * needed. There is particular consideration for checking a release date of
11178
+ * software, as the license for DataTables Plus is perpetual for the version
11179
+ * purchased, and it shouldn't show a message for the purchased version ever.
11180
+ *
11181
+ * @param releaseDate The date the software was released on.
11182
+ * @param software The software name being validated. Can be null for a general
11183
+ * "Plus" check.
11184
+ * @returns true if valid, false otherwise
11185
+ */
11186
+ function check(releaseDate, software) {
11187
+ let expires = _licenseInfo.expires;
11188
+ if (_licenseInfo.valid === false) {
11189
+ noticePrep('License key invalid');
11190
+ noticeDisplay();
11191
+ }
11192
+ else if (_licenseInfo.type === 'trial') {
11193
+ // Trail is for plus, so the software type isn't taken into account
11194
+ let remaining = expires
11195
+ ? Math.ceil((expires.getTime() - new Date().getTime()) / 86400000)
11196
+ : -1;
11197
+ if (remaining < 0) {
11198
+ // Trial expires
11199
+ consoleMsg('Your trial has now expired - https://datatables.net/plus', 'warn');
11200
+ noticePrep('Trial expired');
11201
+ noticeDisplay();
11202
+ return false;
11203
+ }
11204
+ else {
11205
+ // Let the user know when it is going to expire with a console
11206
+ // message.
11207
+ consoleMsg('Your trial expires in ' +
11208
+ remaining +
11209
+ ' day' +
11210
+ (remaining === 1 ? '' : 's'));
11211
+ return true;
11212
+ }
11213
+ }
11214
+ else if (_licenseInfo.type === 'plus' ||
11215
+ (_licenseInfo.type === 'editor' && software === 'editor')) {
11216
+ if (!expires || new Date(releaseDate) > expires) {
11217
+ noticePrep('Upgrade required for this version');
11218
+ noticeDisplay();
11219
+ return false;
11220
+ }
11221
+ return true;
11222
+ }
11223
+ else if (_licenseInfo.type === 'editor' && software !== 'editor') {
11224
+ noticePrep('License for Editor only. Upgrade for Plus');
11225
+ noticeDisplay();
11226
+ return false;
11227
+ }
11228
+ noticePrep();
11229
+ noticeDisplay();
11230
+ return false;
11231
+ }
11232
+ /**
11233
+ * Common log message handling
11234
+ *
11235
+ * @param msg Message to show
11236
+ * @param level Log level
11237
+ */
11238
+ function consoleMsg(msg, level = 'log') {
11239
+ let fn = level === 'log' ? console.log : console.warn;
11240
+ fn('%cDataTables Plus%c ' + msg, 'background: #007bff; color: #fff; padding: 2px 5px;', 'color: inherit;');
11241
+ }
11242
+ /**
11243
+ * Set the DataTables Plus key to use
11244
+ *
11245
+ * @param key DataTables Plus key - obtain from https://datatables.net/account .
11246
+ */
11247
+ const key = function (key) {
11248
+ _processingKey = true;
11249
+ // Run the verification of the key
11250
+ verify(key)
11251
+ .then(result => {
11252
+ _processingKey = false;
11253
+ check(_delayedReleaseDate, _delayedSoftware);
11254
+ })
11255
+ .catch(() => {
11256
+ _processingKey = false;
11257
+ check(_delayedReleaseDate, _delayedSoftware);
11258
+ });
11259
+ };
11260
+ /**
11261
+ * Build the notice
11262
+ *
11263
+ * @returns
11264
+ */
11265
+ function noticePrep(text) {
11266
+ if (!_ready) {
11267
+ let shadow = _wm[0].attachShadow({ mode: 'closed' });
11268
+ let notice = Dom.c('div').css({
11269
+ position: 'fixed',
11270
+ bottom: '1em',
11271
+ right: '1em',
11272
+ border: '1px solid #ffc107',
11273
+ background: '#fff3cd',
11274
+ color: '#856404',
11275
+ padding: '0.5em 1em',
11276
+ 'font-family': 'sans-serif',
11277
+ 'font-size': '12px',
11278
+ 'border-radius': '4px',
11279
+ 'z-index': '10000',
11280
+ 'box-shadow': '0 2px 5px rgba(0,0,0,0.2)'
11281
+ });
11282
+ Dom.c('a')
11283
+ .attr('href', 'https://datatables.net/tn/25')
11284
+ .attr('target', '_blank')
11285
+ .css({
11286
+ color: 'inherit',
11287
+ 'text-decoration': 'none'
11288
+ })
11289
+ .appendTo(notice);
11290
+ if (!text) {
11291
+ text = 'License key required';
11292
+ }
11293
+ shadow.appendChild(notice[0]);
11294
+ _notice = notice;
11295
+ _ready = true;
11296
+ }
11297
+ if (text) {
11298
+ _notice
11299
+ .find('a')
11300
+ .html('DataTables Plus: ' + text + ' - learn more &#187;');
11301
+ }
11302
+ }
11303
+ /**
11304
+ * Display the license notice
11305
+ */
11306
+ function noticeDisplay() {
11307
+ if (!_processingKey && !document.body.contains(_wm[0])) {
11308
+ document.body.appendChild(_wm[0]);
11309
+ }
11310
+ }
11311
+ /**
11312
+ * Validate the license string, which is in two parts - the first is a payload
11313
+ * that provides a small amount of information about the license, and the second
11314
+ * which is the license key.
11315
+ *
11316
+ * @param licenseString Key to validate
11317
+ * @returns Promise with validation information
11318
+ */
11319
+ function verify(licenseString) {
11320
+ return new Promise(function (resolve) {
11321
+ try {
11322
+ var parts = licenseString.split(':');
11323
+ if (parts.length !== 2) {
11324
+ _licenseInfo.valid = false;
11325
+ return resolve();
11326
+ }
11327
+ var payload = parts[0];
11328
+ var signatureB64 = parts[1];
11329
+ // Backwards compat for old browsers
11330
+ var cryptoObj = window.crypto || window.msCrypto;
11331
+ var subtle = cryptoObj.subtle || cryptoObj.webkitSubtle;
11332
+ var rawKey = b64ToBuf(_publicKey);
11333
+ var rawSig = b64ToBuf(signatureB64);
11334
+ var data = new TextEncoder().encode(payload);
11335
+ subtle
11336
+ .importKey('raw', rawKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify'])
11337
+ .then(function (key) {
11338
+ return subtle.verify({ name: 'ECDSA', hash: { name: 'SHA-256' } }, key, rawSig, data);
11339
+ })
11340
+ .then(function (isValid) {
11341
+ if (!isValid) {
11342
+ _licenseInfo.valid = false;
11343
+ return resolve();
11344
+ }
11345
+ // Extract the payload to be useful
11346
+ var payloadParts = payload.match(/(plus|trial|editor)_(\d+)_(\d{4})(\d{2})(\d{2})/);
11347
+ if (!payloadParts || payloadParts.length !== 6) {
11348
+ _licenseInfo.valid = false;
11349
+ return resolve();
11350
+ }
11351
+ _licenseInfo.valid = true;
11352
+ _licenseInfo.type = payloadParts[1];
11353
+ _licenseInfo.developers = parseInt(payloadParts[2]);
11354
+ _licenseInfo.expires = new Date(payloadParts[3] +
11355
+ '-' +
11356
+ payloadParts[4] +
11357
+ '-' +
11358
+ payloadParts[5]);
11359
+ resolve();
11360
+ })
11361
+ .catch(function () {
11362
+ _licenseInfo.valid = false;
11363
+ resolve();
11364
+ });
11365
+ }
11366
+ catch (e) {
11367
+ _licenseInfo.valid = false;
11368
+ resolve();
11369
+ }
11370
+ });
11371
+ }
11372
+ /**
11373
+ * Create the `plus` function on `DataTable` which Plus extensions can call to
11374
+ * determine if the license key is valid and in date for the release. The
11375
+ * resulting function is called like this: `DataTable.plus('2026-12-25')` and
11376
+ * will return `true` or `false` depending on the key that was given (or not).
11377
+ *
11378
+ * @param DataTable The DataTable host object
11379
+ */
11380
+ function plus (DataTable) {
11381
+ Object.defineProperty(DataTable, 'plus', {
11382
+ value: function (releaseDate, software = null) {
11383
+ // Unsecure sites are only useful for development, so allow there
11384
+ // and on the site.
11385
+ let host = window.location.hostname;
11386
+ let isDev = host === '192.168.234.234' ||
11387
+ host.endsWith('.datatables.net') ||
11388
+ host === 'datatables.net';
11389
+ if (isDev) {
11390
+ return true;
11391
+ }
11392
+ if (_processingKey) {
11393
+ // The validation of the key is async, so there is a chance that
11394
+ // it could still be happening when this runs. We just queue the
11395
+ // last one if that is the case.
11396
+ _delayedReleaseDate = releaseDate;
11397
+ _delayedSoftware = software;
11398
+ return true;
11399
+ }
11400
+ return check(releaseDate, software);
11401
+ },
11402
+ configurable: false,
11403
+ enumerable: false,
11404
+ writable: false
11405
+ });
11406
+ }
11407
+
11020
11408
  /**
11021
11409
  * CommonJS factory function pass through. This will check if the arguments
11022
11410
  * given are a window object or a jQuery object. If so they are set accordingly.
@@ -11376,8 +11764,7 @@ register$2('pageLength', function (settings, optsIn) {
11376
11764
  }
11377
11765
  // Wrapper element - use a span as a holder for where the select will go
11378
11766
  var tmpId = 'tmp-' + +new Date();
11379
- var div = Dom
11380
- .c('div')
11767
+ var div = Dom.c('div')
11381
11768
  .classAdd(classes.container)
11382
11769
  .html(str.replace('_MENU_', '<span id="' + tmpId + '"></span>'));
11383
11770
  // Save text node content for macro updating
@@ -11399,9 +11786,9 @@ register$2('pageLength', function (settings, optsIn) {
11399
11786
  });
11400
11787
  };
11401
11788
  // Next, the select itself, along with the options
11402
- var select = Dom
11403
- .c('select')
11789
+ var select = Dom.c('select')
11404
11790
  .attr('aria-controls', tableId)
11791
+ .attr('autocomplete', 'off')
11405
11792
  .classAdd(classes.select);
11406
11793
  for (i = 0; i < lengths.length; i++) {
11407
11794
  // Attempt to look up the length from the i18n options
@@ -11432,7 +11819,26 @@ register$2('pageLength', function (settings, optsIn) {
11432
11819
  // Update node value whenever anything changes the table's length
11433
11820
  Dom.s(settings.table).on('length.dt.DT', function (e, s, len) {
11434
11821
  if (settings === s) {
11435
- div.find('select').val(len);
11822
+ let localSelect = div.find('select');
11823
+ // Remove any temporary values
11824
+ localSelect.find('option[data-dt-len-tmp]').remove();
11825
+ let option = localSelect.find('option[value="' + len + '"]');
11826
+ // If the select list doesn't have the target value, then we
11827
+ // need to add it for display.
11828
+ if (!option.length) {
11829
+ let after = findInsertBeforePoint(select, len);
11830
+ let tempOption = Dom.c('option')
11831
+ .val(len)
11832
+ .text(len)
11833
+ .attr('data-dt-len-tmp', true);
11834
+ if (after && after.length) {
11835
+ tempOption.insertBefore(after);
11836
+ }
11837
+ else {
11838
+ localSelect.append(tempOption);
11839
+ }
11840
+ }
11841
+ localSelect.val(len);
11436
11842
  // Resolve plurals in the text for the new length
11437
11843
  updateEntries(len);
11438
11844
  }
@@ -11440,6 +11846,19 @@ register$2('pageLength', function (settings, optsIn) {
11440
11846
  updateEntries(settings.pageLength);
11441
11847
  return div;
11442
11848
  }, 'l');
11849
+ /**
11850
+ * Find the element to insert the temporary option before to keep the sequence.
11851
+ *
11852
+ * @param select Select element
11853
+ * @param insertValue Page length value
11854
+ * @returns Target option or null if not found
11855
+ */
11856
+ function findInsertBeforePoint(select, insertValue) {
11857
+ let options = select.find('option');
11858
+ let values = options.mapTo(el => parseInt(el.value));
11859
+ let idx = values.findIndex(val => val > insertValue);
11860
+ return idx < -1 ? null : options.eq(idx);
11861
+ }
11443
11862
 
11444
11863
  let __searchCounter = 0;
11445
11864
  register$2('search', function (settings, optsIn) {
@@ -11450,7 +11869,9 @@ register$2('search', function (settings, optsIn) {
11450
11869
  let classes = settings.classes.search;
11451
11870
  let tableId = settings.tableId;
11452
11871
  let language = settings.language;
11453
- let input = '<input type="search" class="' + classes.input + '"/>';
11872
+ let input = '<input type="search" class="' +
11873
+ classes.input +
11874
+ '" autocomplete="off"/>';
11454
11875
  let opts = util.object.assignDeep({
11455
11876
  columns: '*',
11456
11877
  placeholder: language.searchPlaceholder,
@@ -12272,6 +12693,8 @@ DataTable.datetime = datetime;
12272
12693
  DataTable.__browser = browser;
12273
12694
  DataTable.Dom = Dom;
12274
12695
  DataTable.ajax = util.ajax;
12696
+ DataTable.key = key;
12697
+ plus(DataTable);
12275
12698
  /**
12276
12699
  * Private data store, containing all of the settings objects that are created
12277
12700
  * for the tables on a given page.