PageSourceSearch

https://fintechtradingcompetition.com/articles/scoreboard_files/datatables-binding-0.34.0/datatables.js

js fintechtradingcompetition.com collected 2026-09-28 06:43:46 UTC 61,924 bytes, 1,539 lines download raw bytes

1(function() {
2
3// some helper functions: using a global object DTWidget so that it can be used
4// in JS() code, e.g. datatable(options = list(foo = JS('code'))); unlike R's
5// dynamic scoping, when 'code' is eval'ed, JavaScript does not know objects
6// from the "parent frame", e.g. JS('DTWidget') will not work unless it was made
7// a global object
8var DTWidget = {};
9
10// 123456666.7890 -> 123,456,666.7890
11var markInterval = function(d, digits, interval, mark, decMark, precision) {
12  x = precision ? d.toPrecision(digits) : d.toFixed(digits);
13  if (!/^-?[\d.]+$/.test(x)) return x;
14  var xv = x.split('.');
15  if (xv.length > 2) return x;  // should have at most one decimal point
16  xv[0] = xv[0].replace(new RegExp('\\B(?=(\\d{' + interval + '})+(?!\\d))', 'g'), mark);
17  return xv.join(decMark);
18};
19
20DTWidget.formatCurrency = function(data, currency, digits, interval, mark, decMark, before, zeroPrint) {
21  var d = parseFloat(data);
22  if (isNaN(d)) return '';
23  if (zeroPrint !== null && d === 0.0) return zeroPrint;
24  var res = markInterval(d, digits, interval, mark, decMark);
25  res = before ? (/^-/.test(res) ? '-' + currency + res.replace(/^-/, '') : currency + res) :
26    res + currency;
27  return res;
28};
29
30DTWidget.formatString = function(data, prefix, suffix) {
31  var d = data;
32  if (d === null) return '';
33  return prefix + d + suffix;
34};
35
36DTWidget.formatPercentage = function(data, digits, interval, mark, decMark, zeroPrint) {
37  var d = parseFloat(data);
38  if (isNaN(d)) return '';
39  if (zeroPrint !== null && d === 0.0) return zeroPrint;
40  return markInterval(d * 100, digits, interval, mark, decMark) + '%';
41};
42
43DTWidget.formatRound = function(data, digits, interval, mark, decMark, zeroPrint) {
44  var d = parseFloat(data);
45  if (isNaN(d)) return '';
46  if (zeroPrint !== null && d === 0.0) return zeroPrint;
47  return markInterval(d, digits, interval, mark, decMark);
48};
49
50DTWidget.formatSignif = function(data, digits, interval, mark, decMark, zeroPrint) {
51  var d = parseFloat(data);
52  if (isNaN(d)) return '';
53  if (zeroPrint !== null && d === 0.0) return zeroPrint;
54  return markInterval(d, digits, interval, mark, decMark, true);
55};
56
57DTWidget.formatDate = function(data, method, params) {
58  var d = data;
59  if (d === null) return '';
60  // (new Date('2015-10-28')).toDateString() may return 2015-10-27 because the
61  // actual time created could be like 'Tue Oct 27 2015 19:00:00 GMT-0500 (CDT)',
62  // i.e. the date-only string is treated as UTC time instead of local time
63  if ((method === 'toDateString' || method === 'toLocaleDateString') && /^\d{4,}\D\d{2}\D\d{2}$/.test(d)) {
64    d = d.split(/\D/);
65    d = new Date(d[0], d[1] - 1, d[2]);
66  } else {
67    d = new Date(d);
68  }
69  return d[method].apply(d, params);
70};
71
72window.DTWidget = DTWidget;
73
74// A helper function to update the properties of existing filters
75var setFilterProps = function(td, props) {
76  // Update enabled/disabled state
77  var $input = $(td).find('input').first();
78  var searchable = $input.data('searchable');
79  $input.prop('disabled', !searchable || props.disabled);
80
81  // Based on the filter type, set its new values
82  var type = td.getAttribute('data-type');
83  if (['factor', 'logical'].includes(type)) {
84    // Reformat the new dropdown options for use with selectize
85    var new_vals = props.params.options.map(function(item) {
86      return { text: item, value: item };
87    });
88
89    // Find the selectize object
90    var dropdown = $(td).find('.selectized').eq(0)[0].selectize;
91
92    // Note the current values
93    var old_vals = dropdown.getValue();
94
95    // Remove the existing values
96    dropdown.clearOptions();
97
98    // Add the new options
99    dropdown.addOption(new_vals);
100
101    // Preserve the existing values
102    dropdown.setValue(old_vals);
103
104  } else if (['number', 'integer', 'date', 'time'].includes(type)) {
105    // Apply internal scaling to new limits. Updating scale not yet implemented.
106    var slider = $(td).find('.noUi-target').eq(0);
107    var scale = Math.pow(10, Math.max(0, +slider.data('scale') || 0));
108    var new_vals = [props.params.min * scale, props.params.max * scale];
109
110    // Note what the new limits will be just for this filter
111    var new_lims = new_vals.slice();
112
113    // Determine the current values and limits
114    var old_vals = slider.val().map(Number);
115    var old_lims = slider.noUiSlider('options').range;
116    old_lims = [old_lims.min, old_lims.max];
117
118    // Preserve the current values if filters have been applied; otherwise, apply no filtering
119    if (old_vals[0] != old_lims[0]) {
120      new_vals[0] = Math.max(old_vals[0], new_vals[0]);
121    }
122
123    if (old_vals[1] != old_lims[1]) {
124      new_vals[1] = Math.min(old_vals[1], new_vals[1]);
125    }
126
127    // Update the endpoints of the slider
128    slider.noUiSlider({
129      start: new_vals,
130      range: {'min': new_lims[0], 'max': new_lims[1]}
131    }, true);
132  }
133};
134
135var transposeArray2D = function(a) {
136  return a.length === 0 ? a : HTMLWidgets.transposeArray2D(a);
137};
138
139var crosstalkPluginsInstalled = false;
140
141function maybeInstallCrosstalkPlugins() {
142  if (crosstalkPluginsInstalled)
143    return;
144  crosstalkPluginsInstalled = true;
145
146  $.fn.dataTable.ext.afnFiltering.push(
147    function(oSettings, aData, iDataIndex) {
148      var ctfilter = oSettings.nTable.ctfilter;
149      if (ctfilter && !ctfilter[iDataIndex])
150        return false;
151
152      var ctselect = oSettings.nTable.ctselect;
153      if (ctselect && !ctselect[iDataIndex])
154        return false;
155
156      return true;
157    }
158  );
159}
160
161HTMLWidgets.widget({
162  name: "datatables",
163  type: "output",
164  renderOnNullValue: true,
165  initialize: function(el, width, height) {
166    // in order that the type=number inputs return a number
167    $.valHooks.number = {
168      get: function(el) {
169        var value = parseFloat(el.value);
170        return isNaN(value) ? "" : value;
171      }
172    };
173    $(el).html(' ');
174    return {
175      data: null,
176      ctfilterHandle: new crosstalk.FilterHandle(),
177      ctfilterSubscription: null,
178      ctselectHandle: new crosstalk.SelectionHandle(),
179      ctselectSubscription: null
180    };
181  },
182  renderValue: function(el, data, instance) {
183    if ((el.offsetWidth === 0 || el.offsetHeight === 0) && data.lazyRender !== false) {
184      instance.data = data;
185      return;
186    }
187    instance.data = null;
188    var $el = $(el);
189    $el.empty();
190
191    if (data === null) {
192      $el.append(' ');
193      // clear previous Shiny inputs (if any)
194      for (var i in instance.clearInputs) instance.clearInputs[i]();
195      instance.clearInputs = {};
196      return;
197    }
198
199    var crosstalkOptions = data.crosstalkOptions;
200    if (!crosstalkOptions) crosstalkOptions = {
201      'key': null, 'group': null
202    };
203    if (crosstalkOptions.group) {
204      maybeInstallCrosstalkPlugins();
205      instance.ctfilterHandle.setGroup(crosstalkOptions.group);
206      instance.ctselectHandle.setGroup(crosstalkOptions.group);
207    }
208
209    // if we are in the viewer then we always want to fillContainer and
210    // and autoHideNavigation (unless the user has explicitly set these)
211    if (window.HTMLWidgets.viewerMode) {
212      if (!data.hasOwnProperty("fillContainer"))
213        data.fillContainer = true;
214      if (!data.hasOwnProperty("autoHideNavigation"))
215        data.autoHideNavigation = true;
216    }
217
218    // propagate fillContainer to instance (so we have it in resize)
219    instance.fillContainer = data.fillContainer;
220
221    var cells = data.data;
222
223    if (cells instanceof Array) cells = transposeArray2D(cells);
224
225    $el.append(data.container);
226    var $table = $el.find('table');
227    if (data.class) $table.addClass(data.class);
228    if (data.caption) $table.prepend(data.caption);
229
230    if (!data.selection) data.selection = {
231      mode: 'none', selected: null, target: 'row', selectable: null
232    };
233    if (HTMLWidgets.shinyMode && data.selection.mode !== 'none' &&
234        data.selection.target === 'row+column') {
235      if ($table.children('tfoot').length === 0) {
236        $table.append($('<tfoot>'));
237        $table.find('thead tr').clone().appendTo($table.find('tfoot'));
238      }
239    }
240
241    // column filters
242    var filterRow;
243    switch (data.filter) {
244      case 'top':
245        $table.children('thead').append(data.filterHTML);
246        filterRow = $table.find('thead tr:last td');
247        break;
248      case 'bottom':
249        if ($table.children('tfoot').length === 0) {
250          $table.append($('<tfoot>'));
251        }
252        $table.children('tfoot').prepend(data.filterHTML);
253        filterRow = $table.find('tfoot tr:first td');
254        break;
255    }
256
257    var options = { searchDelay: 1000 };
258    if (cells !== null) $.extend(options, {
259      data: cells
260    });
261
262    // options for fillContainer
263    var bootstrapActive = typeof($.fn.popover) != 'undefined';
264    if (instance.fillContainer) {
265
266      // force scrollX/scrollY and turn off autoWidth
267      options.scrollX = true;
268      options.scrollY = "100px"; // can be any value, we'll adjust below
269
270      // if we aren't paginating then move around the info/filter controls
271      // to save space at the bottom and rephrase the info callback
272      if (data.options.paging === false) {
273
274        // we know how to do this cleanly for bootstrap, not so much
275        // for other themes/layouts
276        if (bootstrapActive) {
277          options.dom = "<'row'<'col-sm-4'i><'col-sm-8'f>>" +
278                        "<'row'<'col-sm-12'tr>>";
279        }
280
281        options.fnInfoCallback = function(oSettings, iStart, iEnd,
282                                           iMax, iTotal, sPre) {
283          return Number(iTotal).toLocaleString() + " records";
284        };
285      }
286    }
287
288    // auto hide navigation if requested
289    // Note, this only works on client-side processing mode as on server-side,
290    // cells (data.data) is null; In addition, we require the pageLength option
291    // being provided explicitly to enable this. Despite we may be able to deduce
292    // the default value of pageLength, it may complicate things so we'd rather
293    // put this responsiblity to users and warn them on the R side.
294    if (data.autoHideNavigation === true && data.options.paging !== false) {
295      // strip all nav if length >= cells
296      if ((cells instanceof Array) && data.options.pageLength >= cells.length)
297        options.dom = bootstrapActive ? "<'row'<'col-sm-12'tr>>" : "t";
298      // alternatively lean things out for flexdashboard mobile portrait
299      else if (bootstrapActive && window.FlexDashboard && window.FlexDashboard.isMobilePhone())
300        options.dom = "<'row'<'col-sm-12'f>>" +
301                      "<'row'<'col-sm-12'tr>>"  +
302                      "<'row'<'col-sm-12'p>>";
303    }
304
305    $.extend(true, options, data.options || {});
306
307    var searchCols = options.searchCols;
308    if (searchCols) {
309      searchCols = searchCols.map(function(x) {
310        return x === null ? '' : x.search;
311      });
312      // FIXME: this means I don't respect the escapeRegex setting
313      delete options.searchCols;
314    }
315
316    // server-side processing?
317    var server = options.serverSide === true;
318
319    // use the dataSrc function to pre-process JSON data returned from R
320    var DT_rows_all = [], DT_rows_current = [];
321    if (server && HTMLWidgets.shinyMode && typeof options.ajax === 'object' &&
322        /^session\/[\da-z]+\/dataobj/.test(options.ajax.url) && !options.ajax.dataSrc) {
323      options.ajax.dataSrc = function(json) {
324        DT_rows_all = $.makeArray(json.DT_rows_all);
325        DT_rows_current = $.makeArray(json.DT_rows_current);
326        var data = json.data;
327        if (!colReorderEnabled()) return data;
328        var table = $table.DataTable(), order = table.colReorder.order(), flag = true, i, j, row;
329        for (i = 0; i < order.length; ++i) if (order[i] !== i) flag = false;
330        if (flag) return data;
331        for (i = 0; i < data.length; ++i) {
332          row = data[i].slice();
333          for (j = 0; j < order.length; ++j) data[i][j] = row[order[j]];
334        }
335        return data;
336      };
337    }
338
339    var thiz = this;
340    if (instance.fillContainer) $table.on('init.dt', function(e) {
341      thiz.fillAvailableHeight(el, $(el).innerHeight());
342    });
343    // If the page contains serveral datatables and one of which enables colReorder,
344    // the table.colReorder.order() function will exist but throws error when called.
345    // So it seems like the only way to know if colReorder is enabled or not is to
346    // check the options.
347    var colReorderEnabled = function() { return "colReorder" in options; };
348    var table = $table.DataTable(options);
349    $el.data('datatable', table);
350
351    if ('rowGroup' in options) {
352      // Maintain RowGroup dataSrc when columns are reordered (#1109)
353      table.on('column-reorder', function(e, settings, details) {
354        var oldDataSrc = table.rowGroup().dataSrc();
355        var newDataSrc = details.mapping[oldDataSrc];
356        table.rowGroup().dataSrc(newDataSrc);
357      });
358    }
359
360    // Unregister previous Crosstalk event subscriptions, if they exist
361    if (instance.ctfilterSubscription) {
362      instance.ctfilterHandle.off("change", instance.ctfilterSubscription);
363      instance.ctfilterSubscription = null;
364    }
365    if (instance.ctselectSubscription) {
366      instance.ctselectHandle.off("change", instance.ctselectSubscription);
367      instance.ctselectSubscription = null;
368    }
369
370    if (!crosstalkOptions.group) {
371      $table[0].ctfilter = null;
372      $table[0].ctselect = null;
373    } else {
374      var key = crosstalkOptions.key;
375      function keysToMatches(keys) {
376        if (!keys) {
377          return null;
378        } else {
379          var selectedKeys = {};
380          for (var i = 0; i < keys.length; i++) {
381            selectedKeys[keys[i]] = true;
382          }
383          var matches = {};
384          for (var j = 0; j < key.length; j++) {
385            if (selectedKeys[key[j]])
386              matches[j] = true;
387          }
388          return matches;
389        }
390      }
391
392      function applyCrosstalkFilter(e) {
393        $table[0].ctfilter = keysToMatches(e.value);
394        table.draw();
395      }
396      instance.ctfilterSubscription = instance.ctfilterHandle.on("change", applyCrosstalkFilter);
397      applyCrosstalkFilter({value: instance.ctfilterHandle.filteredKeys});
398
399      function applyCrosstalkSelection(e) {
400        if (e.sender !== instance.ctselectHandle) {
401          table
402            .rows('.' + selClass, {search: 'applied'})
403            .nodes()
404            .to$()
405            .removeClass(selClass);
406          if (selectedRows)
407            changeInput('rows_selected', selectedRows(), void 0, true);
408        }
409
410        if (e.sender !== instance.ctselectHandle && e.value && e.value.length) {
411          var matches = keysToMatches(e.value);
412
413          // persistent selection with plotly (& leaflet)
414          var ctOpts = crosstalk.var("plotlyCrosstalkOpts").get() || {};
415          if (ctOpts.persistent === true) {
416            var matches = $.extend(matches, $table[0].ctselect);
417          }
418
419          $table[0].ctselect = matches;
420          table.draw();
421        } else {
422          if ($table[0].ctselect) {
423            $table[0].ctselect = null;
424            table.draw();
425          }
426        }
427      }
428      instance.ctselectSubscription = instance.ctselectHandle.on("change", applyCrosstalkSelection);
429      // TODO: This next line doesn't seem to work when renderDataTable is used
430      applyCrosstalkSelection({value: instance.ctselectHandle.value});
431    }
432
433    var inArray = function(val, array) {
434      return $.inArray(val, $.makeArray(array)) > -1;
435    };
436
437    // search the i-th column
438    var searchColumn = function(i, value) {
439      var regex = false, ci = true;
440      if (options.search) {
441        regex = options.search.regex,
442        ci = options.search.caseInsensitive !== false;
443      }
444      // need to transpose the column index when colReorder is enabled
445      if (table.colReorder) i = table.colReorder.transpose(i);
446      return table.column(i).search(value, regex, !regex, ci);
447    };
448
449    if (data.filter !== 'none') {
450      if (!data.hasOwnProperty('filterSettings')) data.filterSettings = {};
451
452      filterRow.each(function(i, td) {
453
454        var $td = $(td), type = $td.data('type'), filter;
455        var $input = $td.children('div').first().children('input');
456        var disabled = $input.prop('disabled');
457        var searchable = table.settings()[0].aoColumns[i].bSearchable;
458        $input.prop('disabled', !searchable || disabled);
459        $input.data('searchable', searchable); // for updating later
460        $input.on('input blur', function() {
461          $input.next('span').toggle(Boolean($input.val()));
462        });
463        // Bootstrap sets pointer-events to none and we won't be able to click
464        // the clear button
465        $input.next('span').css('pointer-events', 'auto').hide().click(function() {
466          $(this).hide().prev('input').val('').trigger('input').focus();
467        });
468        var searchCol;  // search string for this column
469        if (searchCols && searchCols[i]) {
470          searchCol = searchCols[i];
471          $input.val(searchCol).trigger('input');
472        }
473        var $x = $td.children('div').last();
474
475        // remove the overflow: hidden attribute of the scrollHead
476        // (otherwise the scrolling table body obscures the filters)
477        // The workaround and the discussion from
478        // https://github.com/rstudio/DT/issues/554#issuecomment-518007347
479        // Otherwise the filter selection will not be anchored to the values
480        // when the columns number is many and scrollX is enabled.
481        var scrollHead = $(el).find('.dataTables_scrollHead,.dataTables_scrollFoot');
482        var cssOverflowHead = scrollHead.css('overflow');
483        var scrollBody = $(el).find('.dataTables_scrollBody');
484        var cssOverflowBody = scrollBody.css('overflow');
485        var scrollTable = $(el).find('.dataTables_scroll');
486        var cssOverflowTable = scrollTable.css('overflow');
487        if (cssOverflowHead === 'hidden') {
488          $x.on('show hide', function(e) {
489            if (e.type === 'show') {
490              scrollHead.css('overflow', 'visible');
491              scrollBody.css('overflow', 'visible');
492              scrollTable.css('overflow-x', 'scroll');
493            } else {
494              scrollHead.css('overflow', cssOverflowHead);
495              scrollBody.css('overflow', cssOverflowBody);
496              scrollTable.css('overflow-x', cssOverflowTable);
497            }
498          });
499          $x.css('z-index', 25);
500        }
501
502        if (inArray(type, ['factor', 'logical'])) {
503          $input.on({
504            click: function() {
505              $input.parent().hide(); $x.show().trigger('show'); filter[0].selectize.focus();
506            },
507            input: function() {
508              var v1 = JSON.stringify(filter[0].selectize.getValue()), v2 = $input.val();
509              if (v1 === '[]') v1 = '';
510              if (v1 !== v2) filter[0].selectize.setValue(v2 === '' ? [] : JSON.parse(v2));
511            }
512          });
513          var $input2 = $x.children('select');
514          filter = $input2.selectize($.extend({
515            options: $input2.data('options').map(function(v, i) {
516              return ({text: v, value: v});
517            }),
518            plugins: ['remove_button'],
519            hideSelected: true,
520            onChange: function(value) {
521              if (value === null) value = []; // compatibility with jQuery 3.0
522              $input.val(value.length ? JSON.stringify(value) : '');
523              if (value.length) $input.trigger('input');
524              $input.attr('title', $input.val());
525              if (server) {
526                searchColumn(i, value.length ? JSON.stringify(value) : '').draw();
527                return;
528              }
529              // turn off filter if nothing selected
530              $td.data('filter', value.length > 0);
531              table.draw();  // redraw table, and filters will be applied
532            }
533          }, data.filterSettings.select));
534          filter[0].selectize.on('blur', function() {
535            $x.hide().trigger('hide'); $input.parent().show(); $input.trigger('blur');
536          });
537          filter.next('div').css('margin-bottom', 'auto');
538        } else if (type === 'character') {
539          var fun = function() {
540            searchColumn(i, $input.val()).draw();
541          };
542          // throttle searching for server-side processing
543          var throttledFun = $.fn.dataTable.util.throttle(fun, options.searchDelay);
544          $input.on('input', function(e, immediate) {
545            // always bypass throttling when immediate = true (via the updateSearch method)
546            (immediate || !server) ? fun() : throttledFun();
547          });
548        } else if (inArray(type, ['number', 'integer', 'date', 'time'])) {
549          var $x0 = $x;
550          $x = $x0.children('div').first();
551          $x0.css({
552            'background-color': '#fff',
553            'border': '1px #ddd solid',
554            'border-radius': '4px',
555            'padding': data.vertical ? '35px 20px': '20px 20px 10px 20px'
556          });
557          var $spans = $x0.children('span').css({
558            'margin-top': data.vertical ? '0' : '10px',
559            'white-space': 'nowrap'
560          });
561          var $span1 = $spans.first(), $span2 = $spans.last();
562          var r1 = +$x.data('min'), r2 = +$x.data('max');
563          // when the numbers are too small or have many decimal places, the
564          // slider may have numeric precision problems (#150)
565          var scale = Math.pow(10, Math.max(0, +$x.data('scale') || 0));
566          r1 = Math.round(r1 * scale); r2 = Math.round(r2 * scale);
567          var scaleBack = function(x, scale) {
568            if (scale === 1) return x;
569            var d = Math.round(Math.log(scale) / Math.log(10));
570            // to avoid problems like 3.423/100 -> 0.034230000000000003
571            return (x / scale).toFixed(d);
572          };
573          var slider_min = function() {
574            return filter.noUiSlider('options').range.min;
575          };
576          var slider_max = function() {
577            return filter.noUiSlider('options').range.max;
578          };
579          $input.on({
580            focus: function() {
581              $x0.show().trigger('show');
582              // first, make sure the slider div leaves at least 20px between
583              // the two (slider value) span's
584              $x0.width(Math.max(160, $span1.outerWidth() + $span2.outerWidth() + 20));
585              // then, if the input is really wide or slider is vertical,
586              // make the slider the same width as the input
587              if ($x0.outerWidth() < $input.outerWidth() || data.vertical) {
588                $x0.outerWidth($input.outerWidth());
589              }
590              // make sure the slider div does not reach beyond the right margin
591              if ($(window).width() < $x0.offset().left + $x0.width()) {
592                $x0.offset({
593                  'left': $input.offset().left + $input.outerWidth() - $x0.outerWidth()
594                });
595              }
596            },
597            blur: function() {
598              $x0.hide().trigger('hide');
599            },
600            input: function() {
601              if ($input.val() === '') filter.val([slider_min(), slider_max()]);
602            },
603            change: function() {
604              var v = $input.val().replace(/\s/g, '');
605              if (v === '') return;
606              v = v.split('...');
607              if (v.length !== 2) {
608                $input.parent().addClass('has-error');
609                return;
610              }
611              if (v[0] === '') v[0] = slider_min();
612              if (v[1] === '') v[1] = slider_max();
613              $input.parent().removeClass('has-error');
614              // treat date as UTC time at midnight
615              var strTime = function(x) {
616                var s = type === 'date' ? 'T00:00:00Z' : '';
617                var t = new Date(x + s).getTime();
618                // add 10 minutes to date since it does not hurt the date, and
619                // it helps avoid the tricky floating point arithmetic problems,
620                // e.g. sometimes the date may be a few milliseconds earlier
621                // than the midnight due to precision problems in noUiSlider
622                return type === 'date' ? t + 3600000 : t;
623              };
624              if (inArray(type, ['date', 'time'])) {
625                v[0] = strTime(v[0]);
626                v[1] = strTime(v[1]);
627              }
628              if (v[0] != slider_min()) v[0] *= scale;
629              if (v[1] != slider_max()) v[1] *= scale;
630              filter.val(v);
631            }
632          });
633          var formatDate = function(d) {
634            d = scaleBack(d, scale);
635            if (type === 'number') return d;
636            if (type === 'integer') return parseInt(d);
637            var x = new Date(+d);
638            if (type === 'date') {
639              var pad0 = function(x) {
640                return ('0' + x).substr(-2, 2);
641              };
642              return x.getUTCFullYear() + '-' + pad0(1 + x.getUTCMonth())
643                      + '-' + pad0(x.getUTCDate());
644            } else {
645              return x.toISOString();
646            }
647          };
648          var opts = type === 'date' ? { step: 60 * 60 * 1000 } :
649                     type === 'integer' ? { step: 1 } : {};
650
651          opts.orientation = data.vertical ? 'vertical': 'horizontal';
652          opts.direction = data.vertical ? 'rtl': 'ltr';
653
654          filter = $x.noUiSlider($.extend({
655            start: [r1, r2],
656            range: {min: r1, max: r2},
657            connect: true
658          }, opts, data.filterSettings.slider));
659          if (scale > 1) (function() {
660            var t1 = r1, t2 = r2;
661            var val = filter.val();
662            while (val[0] > r1 || val[1] < r2) {
663              if (val[0] > r1) {
664                t1 -= val[0] - r1;
665              }
666              if (val[1] < r2) {
667                t2 += r2 - val[1];
668              }
669              filter = $x.noUiSlider($.extend({
670                start: [t1, t2],
671                range: {min: t1, max: t2},
672                connect: true
673              }, opts, data.filterSettings.slider), true);
674              val = filter.val();
675            }
676            r1  = t1; r2 = t2;
677          })();
678          // format with active column renderer, if defined
679          var colDef = data.options.columnDefs.find(function(def) {
680            return (def.targets === i || inArray(i, def.targets)) && 'render' in def;
681          });
682          var updateSliderText = function(v1, v2) {
683            // we only know how to use function renderers
684            if (colDef && typeof colDef.render === 'function') {
685              var restore = function(v) {
686                v = scaleBack(v, scale);
687                return inArray(type, ['date', 'time']) ? new Date(+v) : v;
688              }
689              $span1.text(colDef.render(restore(v1), 'display'));
690              $span2.text(colDef.render(restore(v2), 'display'));
691            } else {
692              $span1.text(formatDate(v1));
693              $span2.text(formatDate(v2));
694            }
695          };
696          updateSliderText(r1, r2);
697          var updateSlider = function(e) {
698            var val = filter.val();
699            // turn off filter if in full range
700            $td.data('filter', val[0] > slider_min() || val[1] < slider_max());
701            var v1 = formatDate(val[0]), v2 = formatDate(val[1]), ival;
702            if ($td.data('filter')) {
703              ival = v1 + ' ... ' + v2;
704              $input.attr('title', ival).val(ival).trigger('input');
705            } else {
706              $input.attr('title', '').val('');
707            }
708            updateSliderText(val[0], val[1]);
709            if (e.type === 'slide') return;  // no searching when sliding only
710            if (server) {
711              searchColumn(i, $td.data('filter') ? ival : '').draw();
712              return;
713            }
714            table.draw();
715          };
716          filter.on({
717            set: updateSlider,
718            slide: updateSlider
719          });
720        }
721
722        // server-side processing will be handled by R (or whatever server
723        // language you use); the following code is only needed for client-side
724        // processing
725        if (server) {
726          // if a search string has been pre-set, search now
727          if (searchCol) $input.trigger('input').trigger('change');
728          return;
729        }
730
731        var customFilter = function(settings, data, dataIndex) {
732          // there is no way to attach a search function to a specific table,
733          // and we need to make sure a global search function is not applied to
734          // all tables (i.e. a range filter in a previous table should not be
735          // applied to the current table); we use the settings object to
736          // determine if we want to perform searching on the current table,
737          // since settings.sTableId will be different to different tables
738          if (table.settings()[0] !== settings) return true;
739          // no filter on this column or no need to filter this column
740          if (typeof filter === 'undefined' || !$td.data('filter')) return true;
741
742          var r = filter.val(), v, r0, r1;
743          var i_data = function(i) {
744            if (!colReorderEnabled()) return i;
745            var order = table.colReorder.order(), k;
746            for (k = 0; k < order.length; ++k) if (order[k] === i) return k;
747            return i; // in theory it will never be here...
748          }
749          v = data[i_data(i)];
750          if (type === 'number' || type === 'integer') {
751            v = parseFloat(v);
752            // how to handle NaN? currently exclude these rows
753            if (isNaN(v)) return(false);
754            r0 = parseFloat(scaleBack(r[0], scale))
755            r1 = parseFloat(scaleBack(r[1], scale));
756            if (v >= r0 && v <= r1) return true;
757          } else if (type === 'date' || type === 'time') {
758            v = new Date(v);
759            r0 = new Date(r[0] / scale); r1 = new Date(r[1] / scale);
760            if (v >= r0 && v <= r1) return true;
761          } else if (type === 'factor') {
762            if (r.length === 0 || inArray(v, r)) return true;
763          } else if (type === 'logical') {
764            if (r.length === 0) return true;
765            if (inArray(v === '' ? 'na' : v, r)) return true;
766          }
767          return false;
768        };
769
770        $.fn.dataTable.ext.search.push(customFilter);
771
772        // search for the preset search strings if it is non-empty
773        if (searchCol) $input.trigger('input').trigger('change');
774
775      });
776
777    }
778
779    // highlight search keywords
780    var highlight = function() {
781      var body = $(table.table().body());
782      // removing the old highlighting first
783      body.unhighlight();
784
785      // don't highlight the "not found" row, so we get the rows using the api
786      if (table.rows({ filter: 'applied' }).data().length === 0) return;
787      // highlight global search keywords
788      body.highlight($.trim(table.search()).split(/\s+/));
789      // then highlight keywords from individual column filters
790      if (filterRow) filterRow.each(function(i, td) {
791        var $td = $(td), type = $td.data('type');
792        if (type !== 'character') return;
793        var $input = $td.children('div').first().children('input');
794        var column = table.column(i).nodes().to$(),
795            val = $.trim($input.val());
796        if (type !== 'character' || val === '') return;
797        column.highlight(val.split(/\s+/));
798      });
799    };
800
801    if (options.searchHighlight) {
802      table
803      .on('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth', highlight)
804      .on('destroy', function() {
805        // remove event handler
806        table.off('draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth');
807      });
808
809      // Set the option for escaping regex characters in our search string.  This will be used
810      // for all future matching.
811      jQuery.fn.highlight.options.escapeRegex = (!options.search || !options.search.regex);
812
813      // initial highlight for state saved conditions and initial states
814      highlight();
815    }
816
817    // run the callback function on the table instance
818    if (typeof data.callback === 'function') data.callback(table);
819
820    // double click to edit the cell, row, column, or all cells
821    if (data.editable) table.on('dblclick.dt', 'tbody td', function(e) {
822      // only bring up the editor when the cell itself is dbclicked, and ignore
823      // other dbclick events bubbled up (e.g. from the <input>)
824      if (e.target !== this) return;
825      var target = [], immediate = false;
826      switch (data.editable.target) {
827        case 'cell':
828          target = [this];
829          immediate = true;  // edit will take effect immediately
830          break;
831        case 'row':
832          target = table.cells(table.cell(this).index().row, '*').nodes();
833          break;
834        case 'column':
835          target = table.cells('*', table.cell(this).index().column).nodes();
836          break;
837        case 'all':
838          target = table.cells().nodes();
839          break;
840        default:
841          throw 'The editable parameter must be "cell", "row", "column", or "all"';
842      }
843      var disableCols = data.editable.disable ? data.editable.disable.columns : null;
844      var numericCols = data.editable.numeric;
845      var areaCols = data.editable.area;
846      var dateCols = data.editable.date;
847      for (var i = 0; i < target.length; i++) {
848        (function(cell, current) {
849          var $cell = $(cell), html = $cell.html();
850          var _cell = table.cell(cell), value = _cell.data(), index = _cell.index().column;
851          var $input;
852          if (inArray(index, numericCols)) {
853            $input = $('<input type="number">');
854          } else if (inArray(index, areaCols)) {
855            $input = $('<textarea></textarea>');
856          } else if (inArray(index, dateCols)) {
857            $input = $('<input type="date">');
858          } else {
859            $input = $('<input type="text">');
860          }
861          if (!immediate) {
862            $cell.data('input', $input).data('html', html);
863            $input.attr('title', 'Hit Ctrl+Enter to finish editing, or Esc to cancel');
864          }
865          $input.val(value);
866          if (inArray(index, disableCols)) {
867            $input.attr('readonly', '').css('filter', 'invert(25%)');
868          }
869          $cell.empty().append($input);
870          if (cell === current) $input.focus();
871          $input.css('width', '100%');
872
873          if (immediate) $input.on('blur', function(e) {
874            var valueNew = $input.val();
875            if (valueNew !== value) {
876              _cell.data(valueNew);
877              if (HTMLWidgets.shinyMode) {
878                changeInput('cell_edit', [cellInfo(cell)], 'DT.cellInfo', null, {priority: 'event'});
879              }
880              // for server-side processing, users have to call replaceData() to update the table
881              if (!server) table.draw(false);
882            } else {
883              $cell.html(html);
884            }
885          }).on('keyup', function(e) {
886            // hit Escape to cancel editing
887            if (e.keyCode === 27) $input.trigger('blur');
888          });
889
890          // bulk edit (row, column, or all)
891          if (!immediate) $input.on('keyup', function(e) {
892            var removeInput = function($cell, restore) {
893              $cell.data('input').remove();
894              if (restore) $cell.html($cell.data('html'));
895            }
896            if (e.keyCode === 27) {
897              for (var i = 0; i < target.length; i++) {
898                removeInput($(target[i]), true);
899              }
900            } else if (e.keyCode === 13 && e.ctrlKey) {
901              // Ctrl + Enter
902              var cell, $cell, _cell, cellData = [];
903              for (var i = 0; i < target.length; i++) {
904                cell = target[i]; $cell = $(cell); _cell = table.cell(cell);
905                _cell.data($cell.data('input').val());
906                HTMLWidgets.shinyMode && cellData.push(cellInfo(cell));
907                removeInput($cell, false);
908              }
909              if (HTMLWidgets.shinyMode) {
910                changeInput('cell_edit', cellData, 'DT.cellInfo', null, {priority: "event"});
911              }
912              if (!server) table.draw(false);
913            }
914          });
915        })(target[i], this);
916      }
917    });
918
919    // interaction with shiny
920    if (!HTMLWidgets.shinyMode && !crosstalkOptions.group) return;
921
922    var methods = {};
923    var shinyData = {};
924
925    methods.updateCaption = function(caption) {
926      if (!caption) return;
927      $table.children('caption').replaceWith(caption);
928    }
929
930    // register clear functions to remove input values when the table is removed
931    instance.clearInputs = {};
932
933    var changeInput = function(id, value, type, noCrosstalk, opts) {
934      var event = id;
935      id = el.id + '_' + id;
936      if (type) id = id + ':' + type;
937      // do not update if the new value is the same as old value
938      if (event !== 'cell_edit' && !/_clicked$/.test(event) && shinyData.hasOwnProperty(id) && shinyData[id] === JSON.stringify(value))
939        return;
940      shinyData[id] = JSON.stringify(value);
941      if (HTMLWidgets.shinyMode && Shiny.setInputValue) {
942        Shiny.setInputValue(id, value, opts);
943        if (!instance.clearInputs[id]) instance.clearInputs[id] = function() {
944          Shiny.setInputValue(id, null);
945        }
946      }
947
948      // HACK
949      if (event === "rows_selected" && !noCrosstalk) {
950        if (crosstalkOptions.group) {
951          var keys = crosstalkOptions.key;
952          var selectedKeys = null;
953          if (value) {
954            selectedKeys = [];
955            for (var i = 0; i < value.length; i++) {
956              // The value array's contents use 1-based row numbers, so we must
957              // convert to 0-based before indexing into the keys array.
958              selectedKeys.push(keys[value[i] - 1]);
959            }
960          }
961          instance.ctselectHandle.set(selectedKeys);
962        }
963      }
964    };
965
966    var addOne = function(x) {
967      return x.map(function(i) { return 1 + i; });
968    };
969
970    var unique = function(x) {
971      var ux = [];
972      $.each(x, function(i, el){
973        if ($.inArray(el, ux) === -1) ux.push(el);
974      });
975      return ux;
976    }
977
978    // change the row index of a cell
979    var tweakCellIndex = function(cell) {
980      var info = cell.index();
981      // some cell may not be valid. e.g, #759
982      // when using the RowGroup extension, datatables will
983      // generate the row label and the cells are not part of
984      // the data thus contain no row/col info
985      if (info === undefined)
986        return {row: null, col: null};
987      if (server) {
988        info.row = DT_rows_current[info.row];
989      } else {
990        info.row += 1;
991      }
992      return {row: info.row, col: info.column};
993    }
994
995    var cleanSelectedValues = function() {
996      changeInput('rows_selected', []);
997      changeInput('columns_selected', []);
998      changeInput('cells_selected', transposeArray2D([]), 'shiny.matrix');
999    }
1000    // #828 we should clean the selection on the server-side when the table reloads
1001    cleanSelectedValues();
1002
1003    // a flag to indicates if select extension is initialized or not
1004    var flagSelectExt = table.settings()[0]._select !== undefined;
1005    // the Select extension should only be used in the client mode and
1006    // when the selection.mode is set to none
1007    if (data.selection.mode === 'none' && !server && flagSelectExt) {
1008      var updateRowsSelected = function() {
1009        var rows = table.rows({selected: true});
1010        var selected = [];
1011        $.each(rows.indexes().toArray(), function(i, v) {
1012          selected.push(v + 1);
1013        });
1014        changeInput('rows_selected', selected);
1015      }
1016      var updateColsSelected = function() {
1017        var columns = table.columns({selected: true});
1018        changeInput('columns_selected', columns.indexes().toArray());
1019      }
1020      var updateCellsSelected = function() {
1021        var cells = table.cells({selected: true});
1022        var selected = [];
1023        cells.every(function() {
1024          var row = this.index().row;
1025          var col = this.index().column;
1026          selected = selected.concat([[row + 1, col]]);
1027        });
1028        changeInput('cells_selected', transposeArray2D(selected), 'shiny.matrix');
1029      }
1030      table.on('select deselect', function(e, dt, type, indexes) {
1031        updateRowsSelected();
1032        updateColsSelected();
1033        updateCellsSelected();
1034      })
1035      updateRowsSelected();
1036      updateColsSelected();
1037      updateCellsSelected();
1038    }
1039
1040    var selMode = data.selection.mode, selTarget = data.selection.target;
1041    var selDisable = data.selection.selectable === false;
1042    if (inArray(selMode, ['single', 'multiple'])) {
1043      var selClass = inArray(data.style, ['bootstrap', 'bootstrap4']) ? 'active' : 'selected';
1044      // selected1: row indices; selected2: column indices
1045      var initSel = function(x) {
1046        if (x === null || typeof x === 'boolean' || selTarget === 'cell') {
1047          return {rows: [], cols: []};
1048        } else if (selTarget === 'row') {
1049          return {rows: $.makeArray(x), cols: []};
1050        } else if (selTarget === 'column') {
1051          return {rows: [], cols: $.makeArray(x)};
1052        } else if (selTarget === 'row+column') {
1053          return {rows: $.makeArray(x.rows), cols: $.makeArray(x.cols)};
1054        }
1055      }
1056      var selected = data.selection.selected;
1057      var selected1 = initSel(selected).rows, selected2 = initSel(selected).cols;
1058      // selectable should contain either all positive or all non-positive values, not both
1059      // positive values indicate "selectable" while non-positive values means "nonselectable"
1060      // the assertion is performed on R side. (only column indicides could be zero which indicates
1061      // the row name)
1062      var selectable = data.selection.selectable;
1063      var selectable1 = initSel(selectable).rows, selectable2 = initSel(selectable).cols;
1064
1065      // After users reorder the rows or filter the table, we cannot use the table index
1066      // directly. Instead, we need this function to find out the rows between the two clicks.
1067      // If user filter the table again between the start click and the end click, the behavior
1068      // would be undefined, but it should not be a problem.
1069      var shiftSelRowsIndex = function(start, end) {
1070        var indexes = server ? DT_rows_all : table.rows({ search: 'applied' }).indexes().toArray();
1071        start = indexes.indexOf(start); end = indexes.indexOf(end);
1072        // if start is larger than end, we need to swap
1073        if (start > end) {
1074          var tmp = end; end = start; start = tmp;
1075        }
1076        return indexes.slice(start, end + 1);
1077      }
1078
1079      var serverRowIndex = function(clientRowIndex) {
1080        return server ? DT_rows_current[clientRowIndex] : clientRowIndex + 1;
1081      }
1082
1083      // row, column, or cell selection
1084      var lastClickedRow;
1085      if (inArray(selTarget, ['row', 'row+column'])) {
1086        // Get the current selected rows. It will also
1087        // update the selected1's value based on the current row selection state
1088        // Note we can't put this function inside selectRows() directly,
1089        // the reason is method.selectRows() will override selected1's value but this
1090        // function will add rows to selected1 (keep the existing selection), which is
1091        // inconsistent with column and cell selection.
1092        var selectedRows = function() {
1093          var rows = table.rows('.' + selClass);
1094          var idx = rows.indexes().toArray();
1095          if (!server) {
1096            selected1 = addOne(idx);
1097            return selected1;
1098          }
1099          idx = idx.map(function(i) {
1100            return DT_rows_current[i];
1101          });
1102          selected1 = selMode === 'multiple' ? unique(selected1.concat(idx)) : idx;
1103          return selected1;
1104        }
1105        // Change selected1's value based on selectable1, then refresh the row state
1106        var onlyKeepSelectableRows = function() {
1107          if (selDisable) { // users can't select; useful when only want backend select
1108            selected1 = [];
1109            return;
1110          }
1111          if (selectable1.length === 0) return;
1112          var nonselectable = selectable1[0] <= 0;
1113          if (nonselectable) {
1114            // should make selectable1 positive
1115            selected1 = $(selected1).not(selectable1.map(function(i) { return -i; })).get();
1116          } else {
1117            selected1 = $(selected1).filter(selectable1).get();
1118          }
1119        }
1120        // Change selected1's value based on selectable1, then
1121        // refresh the row selection state according to values in selected1
1122        var selectRows = function(ignoreSelectable) {
1123          if (!ignoreSelectable) onlyKeepSelectableRows();
1124          table.$('tr.' + selClass).removeClass(selClass);
1125          if (selected1.length === 0) return;
1126          if (server) {
1127            table.rows({page: 'current'}).every(function() {
1128              if (inArray(DT_rows_current[this.index()], selected1)) {
1129                $(this.node()).addClass(selClass);
1130              }
1131            });
1132          } else {
1133            var selected0 = selected1.map(function(i) { return i - 1; });
1134            $(table.rows(selected0).nodes()).addClass(selClass);
1135          }
1136        }
1137        table.on('mousedown.dt', 'tbody tr', function(e) {
1138          var $this = $(this), thisRow = table.row(this);
1139          if (selMode === 'multiple') {
1140            if (e.shiftKey && lastClickedRow !== undefined) {
1141              // select or de-select depends on the last clicked row's status
1142              var flagSel = !$this.hasClass(selClass);
1143              var crtClickedRow = serverRowIndex(thisRow.index());
1144              if (server) {
1145                var rowsIndex = shiftSelRowsIndex(lastClickedRow, crtClickedRow);
1146                // update current page's selClass
1147                rowsIndex.map(function(i) {
1148                  var rowIndex = DT_rows_current.indexOf(i);
1149                  if (rowIndex >= 0) {
1150                    var row = table.row(rowIndex).nodes().to$();
1151                    var flagRowSel = !row.hasClass(selClass);
1152                    if (flagSel === flagRowSel) row.toggleClass(selClass);
1153                  }
1154                });
1155                // update selected1
1156                if (flagSel) {
1157                  selected1 = unique(selected1.concat(rowsIndex));
1158                } else {
1159                  selected1 = selected1.filter(function(index) {
1160                    return !inArray(index, rowsIndex);
1161                  });
1162                }
1163              } else {
1164                // js starts from 0
1165                shiftSelRowsIndex(lastClickedRow - 1, crtClickedRow - 1).map(function(value) {
1166                  var row = table.row(value).nodes().to$();
1167                  var flagRowSel = !row.hasClass(selClass);
1168                  if (flagSel === flagRowSel) row.toggleClass(selClass);
1169                });
1170              }
1171              e.preventDefault();
1172            } else {
1173              $this.toggleClass(selClass);
1174            }
1175          } else {
1176            if ($this.hasClass(selClass)) {
1177              $this.removeClass(selClass);
1178            } else {
1179              table.$('tr.' + selClass).removeClass(selClass);
1180              $this.addClass(selClass);
1181            }
1182          }
1183          if (server && !$this.hasClass(selClass)) {
1184            var id = DT_rows_current[thisRow.index()];
1185            // remove id from selected1 since its class .selected has been removed
1186            if (inArray(id, selected1)) selected1.splice($.inArray(id, selected1), 1);
1187          }
1188          selectedRows(); // update selected1's value based on selClass
1189          selectRows(false); // only keep the selectable rows
1190          changeInput('rows_selected', selected1);
1191          changeInput('row_last_clicked', serverRowIndex(thisRow.index()), null, null, {priority: 'event'});
1192          lastClickedRow = serverRowIndex(thisRow.index());
1193        });
1194        selectRows(false);  // in case users have specified pre-selected rows
1195        // restore selected rows after the table is redrawn (e.g. sort/search/page);
1196        // client-side tables will preserve the selections automatically; for
1197        // server-side tables, we have to *real* row indices are in `selected1`
1198        changeInput('rows_selected', selected1);
1199        if (server) table.on('draw.dt', function(e) { selectRows(false); });
1200        methods.selectRows = function(selected, ignoreSelectable) {
1201          selected1 = $.makeArray(selected);
1202          selectRows(ignoreSelectable);
1203          changeInput('rows_selected', selected1);
1204        }
1205      }
1206
1207      if (inArray(selTarget, ['column', 'row+column'])) {
1208        if (selTarget === 'row+column') {
1209          $(table.columns().footer()).css('cursor', 'pointer');
1210        }
1211        // update selected2's value based on selectable2
1212        var onlyKeepSelectableCols = function() {
1213          if (selDisable) { // users can't select; useful when only want backend select
1214            selected2 = [];
1215            return;
1216          }
1217          if (selectable2.length === 0) return;
1218          var nonselectable = selectable2[0] <= 0;
1219          if (nonselectable) {
1220            // need to make selectable2 positive
1221            selected2 = $(selected2).not(selectable2.map(function(i) { return -i; })).get();
1222          } else {
1223            selected2 = $(selected2).filter(selectable2).get();
1224          }
1225        }
1226        // update selected2 and then
1227        // refresh the col selection state according to values in selected2
1228        var selectCols = function(ignoreSelectable) {
1229          if (!ignoreSelectable) onlyKeepSelectableCols();
1230          // if selected2 is not a valide index (e.g., larger than the column number)
1231          // table.columns(selected2) will fail and result in a blank table
1232          // this is different from the table.rows(), where the out-of-range indexes
1233          // doesn't affect at all
1234          selected2 = $(selected2).filter(table.columns().indexes()).get();
1235          table.columns().nodes().flatten().to$().removeClass(selClass);
1236          if (selected2.length > 0)
1237            table.columns(selected2).nodes().flatten().to$().addClass(selClass);
1238        }
1239        var callback = function() {
1240          var colIdx = selTarget === 'column' ? table.cell(this).index().column :
1241              $.inArray(this, table.columns().footer()),
1242              thisCol = $(table.column(colIdx).nodes());
1243          if (colIdx === -1) return;
1244          if (thisCol.hasClass(selClass)) {
1245            thisCol.removeClass(selClass);
1246            selected2.splice($.inArray(colIdx, selected2), 1);
1247          } else {
1248            if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass);
1249            thisCol.addClass(selClass);
1250            selected2 = selMode === 'single' ? [colIdx] : unique(selected2.concat([colIdx]));
1251          }
1252          selectCols(false); // update selected2 based on selectable
1253          changeInput('columns_selected', selected2);
1254        }
1255        if (selTarget === 'column') {
1256          $(table.table().body()).on('click.dt', 'td', callback);
1257        } else {
1258          $(table.table().footer()).on('click.dt', 'tr th', callback);
1259        }
1260        selectCols(false);  // in case users have specified pre-selected columns
1261        changeInput('columns_selected', selected2);
1262        if (server) table.on('draw.dt', function(e) { selectCols(false); });
1263        methods.selectColumns = function(selected, ignoreSelectable) {
1264          selected2 = $.makeArray(selected);
1265          selectCols(ignoreSelectable);
1266          changeInput('columns_selected', selected2);
1267        }
1268      }
1269
1270      if (selTarget === 'cell') {
1271        var selected3 = [], selectable3 = [];
1272        if (selected !== null) selected3 = selected;
1273        if (selectable !== null && typeof selectable !== 'boolean') selectable3 = selectable;
1274        var findIndex = function(ij, sel) {
1275          for (var i = 0; i < sel.length; i++) {
1276            if (ij[0] === sel[i][0] && ij[1] === sel[i][1]) return i;
1277          }
1278          return -1;
1279        }
1280         // Change selected3's value based on selectable3, then refresh the cell state
1281        var onlyKeepSelectableCells = function() {
1282          if (selDisable) { // users can't select; useful when only want backend select
1283            selected3 = [];
1284            return;
1285          }
1286          if (selectable3.length === 0) return;
1287          var nonselectable = selectable3[0][0] <= 0;
1288          var out = [];
1289          if (nonselectable) {
1290            selected3.map(function(ij) {
1291              // should make selectable3 positive
1292              if (findIndex([-ij[0], -ij[1]], selectable3) === -1) { out.push(ij); }
1293            });
1294          } else {
1295            selected3.map(function(ij) {
1296              if (findIndex(ij, selectable3) > -1) { out.push(ij); }
1297            });
1298          }
1299          selected3 = out;
1300        }
1301        // Change selected3's value based on selectable3, then
1302        // refresh the cell selection state according to values in selected3
1303        var selectCells = function(ignoreSelectable) {
1304          if (!ignoreSelectable) onlyKeepSelectableCells();
1305          table.$('td.' + selClass).removeClass(selClass);
1306          if (selected3.length === 0) return;
1307          if (server) {
1308            table.cells({page: 'current'}).every(function() {
1309              var info = tweakCellIndex(this);
1310              if (findIndex([info.row, info.col], selected3) > -1)
1311                $(this.node()).addClass(selClass);
1312            });
1313          } else {
1314            selected3.map(function(ij) {
1315              $(table.cell(ij[0] - 1, ij[1]).node()).addClass(selClass);
1316            });
1317          }
1318        };
1319        table.on('click.dt', 'tbody td', function() {
1320          var $this = $(this), info = tweakCellIndex(table.cell(this));
1321          if ($this.hasClass(selClass)) {
1322            $this.removeClass(selClass);
1323            selected3.splice(findIndex([info.row, info.col], selected3), 1);
1324          } else {
1325            if (selMode === 'single') $(table.cells().nodes()).removeClass(selClass);
1326            $this.addClass(selClass);
1327            selected3 = selMode === 'single' ? [[info.row, info.col]] :
1328              unique(selected3.concat([[info.row, info.col]]));
1329          }
1330          selectCells(false); // must call this to update selected3 based on selectable3
1331          changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix');
1332        });
1333        selectCells(false);  // in case users have specified pre-selected columns
1334        changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix');
1335
1336        if (server) table.on('draw.dt', function(e) { selectCells(false); });
1337        methods.selectCells = function(selected, ignoreSelectable) {
1338          selected3 = selected ? selected : [];
1339          selectCells(ignoreSelectable);
1340          changeInput('cells_selected', transposeArray2D(selected3), 'shiny.matrix');
1341        }
1342      }
1343    }
1344
1345    // expose some table info to Shiny
1346    var updateTableInfo = function(e, settings) {
1347      // TODO: is anyone interested in the page info?
1348      // changeInput('page_info', table.page.info());
1349      var updateRowInfo = function(id, modifier) {
1350        var idx;
1351        if (server) {
1352          idx = modifier.page === 'current' ? DT_rows_current : DT_rows_all;
1353        } else {
1354          var rows = table.rows($.extend({
1355            search: 'applied',
1356            page: 'all'
1357          }, modifier));
1358          idx = addOne(rows.indexes().toArray());
1359        }
1360        changeInput('rows' + '_' + id, idx);
1361      };
1362      updateRowInfo('current', {page: 'current'});
1363      updateRowInfo('all', {});
1364    }
1365    table.on('draw.dt', updateTableInfo);
1366    updateTableInfo();
1367
1368    // state info
1369    table.on('draw.dt column-visibility.dt', function() {
1370      changeInput('state', table.state());
1371    });
1372    changeInput('state', table.state());
1373
1374    // search info
1375    var updateSearchInfo = function() {
1376      changeInput('search', table.search());
1377      if (filterRow) changeInput('search_columns', filterRow.toArray().map(function(td) {
1378        return $(td).find('input').first().val();
1379      }));
1380    }
1381    table.on('draw.dt', updateSearchInfo);
1382    updateSearchInfo();
1383
1384    var cellInfo = function(thiz) {
1385      var info = tweakCellIndex(table.cell(thiz));
1386      info.value = table.cell(thiz).data();
1387      return info;
1388    }
1389    // the current cell clicked on
1390    table.on('click.dt', 'tbody td', function() {
1391      changeInput('cell_clicked', cellInfo(this), null, null, {priority: 'event'});
1392    })
1393    changeInput('cell_clicked', {});
1394
1395    // do not trigger table selection when clicking on links unless they have classes
1396    table.on('mousedown.dt', 'tbody td a', function(e) {
1397      if (this.className === '') e.stopPropagation();
1398    });
1399
1400    methods.addRow = function(data, rowname, resetPaging) {
1401      var n = table.columns().indexes().length, d = n - data.length;
1402      if (d === 1) {
1403        data = rowname.concat(data)
1404      } else if (d !== 0) {
1405        console.log(data);
1406        console.log(table.columns().indexes());
1407        throw 'New data must be of the same length as current data (' + n + ')';
1408      };
1409      table.row.add(data).draw(resetPaging);
1410    }
1411
1412    methods.updateSearch = function(keywords) {
1413      if (keywords.global !== null)
1414        $(table.table().container()).find('input[type=search]').first()
1415             .val(keywords.global).trigger('input');
1416      var columns = keywords.columns;
1417      if (!filterRow || columns === null) return;
1418      filterRow.toArray().map(function(td, i) {
1419        var v = typeof columns === 'string' ? columns : columns[i];
1420        if (typeof v === 'undefined') {
1421          console.log('The search keyword for column ' + i + ' is undefined')
1422          return;
1423        }
1424        // Update column search string and values on linked filter widgets.
1425        // 'input' for factor and char filters, 'change' for numeric filters.
1426        $(td).find('input').first().val(v).trigger('input', [true]).trigger('change');
1427      });
1428      table.draw();
1429    }
1430
1431    methods.hideCols = function(hide, reset) {
1432      if (reset) table.columns().visible(true, false);
1433      table.columns(hide).visible(false);
1434    }
1435
1436    methods.showCols = function(show, reset) {
1437      if (reset) table.columns().visible(false, false);
1438      table.columns(show).visible(true);
1439    }
1440
1441    methods.colReorder = function(order, origOrder) {
1442      table.colReorder.order(order, origOrder);
1443    }
1444
1445    methods.selectPage = function(page) {
1446      if (table.page.info().pages < page || page < 1) {
1447        throw 'Selected page is out of range';
1448      };
1449      table.page(page - 1).draw(false);
1450    }
1451
1452    methods.reloadData = function(resetPaging, clearSelection) {
1453      // empty selections first if necessary
1454      if (methods.selectRows && inArray('row', clearSelection)) methods.selectRows([]);
1455      if (methods.selectColumns && inArray('column', clearSelection)) methods.selectColumns([]);
1456      if (methods.selectCells && inArray('cell', clearSelection)) methods.selectCells([]);
1457      table.ajax.reload(null, resetPaging);
1458    }
1459
1460    // update table filters (set new limits of sliders)
1461    methods.updateFilters = function(newProps) {
1462      // loop through each filter in the filter row
1463      filterRow.each(function(i, td) {
1464        var k = i;
1465        if (filterRow.length > newProps.length) {
1466          if (i === 0) return;  // first column is row names
1467          k = i - 1;
1468        }
1469        // Update the filters to reflect the updated data.
1470        // Allow "falsy" (e.g. NULL) to signify a no-op.
1471        if (newProps[k]) {
1472          setFilterProps(td, newProps[k]);
1473        }
1474      });
1475    };
1476
1477    table.shinyMethods = methods;
1478  },
1479  resize: function(el, width, height, instance) {
1480    if (instance.data) this.renderValue(el, instance.data, instance);
1481
1482    // dynamically adjust height if fillContainer = TRUE
1483    if (instance.fillContainer)
1484      this.fillAvailableHeight(el, height);
1485
1486    this.adjustWidth(el);
1487  },
1488
1489  // dynamically set the scroll body to fill available height
1490  // (used with fillContainer = TRUE)
1491  fillAvailableHeight: function(el, availableHeight) {
1492
1493    // see how much of the table is occupied by header/footer elements
1494    // and use that to compute a target scroll body height
1495    var dtWrapper = $(el).find('div.dataTables_wrapper');
1496    var dtScrollBody = $(el).find($('div.dataTables_scrollBody'));
1497    var framingHeight = dtWrapper.innerHeight() - dtScrollBody.innerHeight();
1498    var scrollBodyHeight = availableHeight - framingHeight;
1499
1500    // we need to set `max-height` to none as datatables library now sets this
1501    // to a fixed height, disabling the ability to resize to fill the window,
1502    // as it will be set to a fixed 100px under such circumstances, e.g., RStudio IDE,
1503    // or FlexDashboard
1504    // see https://github.com/rstudio/DT/issues/951#issuecomment-1026464509
1505    dtScrollBody.css('max-height', 'none');
1506    // set the height
1507    dtScrollBody.height(scrollBodyHeight + 'px');
1508  },
1509
1510  // adjust the width of columns; remove the hard-coded widths on table and the
1511  // scroll header when scrollX/Y are enabled
1512  adjustWidth: function(el) {
1513    var $el = $(el), table = $el.data('datatable');
1514    if (table) table.columns.adjust();
1515    $el.find('.dataTables_scrollHeadInner').css('width', '')
1516        .children('table').css('margin-left', '');
1517  }
1518});
1519
1520  if (!HTMLWidgets.shinyMode) return;
1521
1522  Shiny.addCustomMessageHandler('datatable-calls', function(data) {
1523    var id = data.id;
1524    var el = document.getElementById(id);
1525    var table = el ? $(el).data('datatable') : null;
1526    if (!table) {
1527      console.log("Couldn't find table with id " + id);
1528      return;
1529    }
1530
1531    var methods = table.shinyMethods, call = data.call;
1532    if (methods[call.method]) {
1533      methods[call.method].apply(table, call.args);
1534    } else {
1535      console.log("Unknown method " + call.method);
1536    }
1537  });
1538
1539})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.