• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gregnb / mui-datatables / #1849

11 Jun 2026 12:42PM UTC coverage: 78.052% (+5.6%) from 72.455%
#1849

push

251 of 353 branches covered (71.1%)

Branch coverage included in aggregate %.

414 of 499 relevant lines covered (82.97%)

34.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.27
/src/MUIDataTable.js
1
import Paper from '@mui/material/Paper';
2
import MuiTable from '@mui/material/Table';
3
import MuiTooltip from '@mui/material/Tooltip';
4
import { withStyles } from 'tss-react/mui';
5
import clsx from 'clsx';
6
import assignwith from 'lodash.assignwith';
7
import cloneDeep from 'lodash.clonedeep';
8
import find from 'lodash.find';
9
import isEqual from 'lodash.isequal';
10
import isUndefined from 'lodash.isundefined';
11
import merge from 'lodash.merge';
12
import PropTypes from 'prop-types';
13
import React from 'react';
14
import DefaultTableBody from './components/TableBody';
15
import DefaultTableFilter from './components/TableFilter';
16
import DefaultTableFilterList from './components/TableFilterList';
17
import DefaultTableFooter from './components/TableFooter';
18
import DefaultTableHead from './components/TableHead';
1✔
19
import DefaultTableResize from './components/TableResize';
20
import DefaultTableToolbar from './components/TableToolbar';
21
import DefaultTableToolbarSelect from './components/TableToolbarSelect';
22
import getTextLabels from './textLabels';
23
import { buildMap, getCollatorComparator, getPageValue, sortCompare, warnDeprecated, warnInfo } from './utils';
24
import { DndProvider } from 'react-dnd';
25
import { HTML5Backend } from 'react-dnd-html5-backend';
26
import { load, save } from './localStorage';
27

28
const defaultTableStyles = theme => ({
29
  root: {
30
    '& .datatables-noprint': {
31
      '@media print': {
32
        display: 'none',
33
      },
34
    },
35
  },
36
  paper: {
37
    isolation: 'isolate',
38
  },
39
  paperResponsiveScrollFullHeightFullWidth: {
1✔
40
    position: 'absolute',
41
  },
42
  tableRoot: {
43
    outline: 'none',
44
  },
45
  responsiveBase: {
46
    overflow: 'auto',
47
    '@media print': {
48
      height: 'auto !important',
49
    },
50
  },
51

52
  // deprecated, but continuing support through v3.x
53
  responsiveScroll: {
54
    overflow: 'auto',
55
    height: '100%',
56
  },
57
  // deprecated, but continuing support through v3.x
58
  responsiveScrollMaxHeight: {
59
    overflow: 'auto',
60
    height: '100%',
61
  },
62
  // deprecated, but continuing support through v3.x
63
  responsiveScrollFullHeight: {
64
    height: '100%',
65
  },
66
  // deprecated, but continuing support through v3.x
67
  responsiveStacked: {
68
    overflow: 'auto',
69
    [theme.breakpoints.down('md')]: {
70
      overflow: 'hidden',
71
    },
72
  },
73
  // deprecated, but continuing support through v3.x
74
  responsiveStackedFullWidth: {},
75
  caption: {
76
    position: 'absolute',
77
    left: '-3000px',
78
  },
79

80
  liveAnnounce: {
81
    border: '0',
82
    clip: 'rect(0 0 0 0)',
83
    height: '1px',
84
    margin: '-1px',
85
    overflow: 'hidden',
86
    padding: '0',
87
    position: 'absolute',
88
    width: '1px',
89
  },
90
});
91

92
const TABLE_LOAD = {
93
  INITIAL: 1,
94
  UPDATE: 2,
95
};
96

97
// Populate this list with anything that might render in the toolbar to determine if we hide the toolbar
98
const TOOLBAR_ITEMS = ['title', 'filter', 'search', 'print', 'download', 'viewColumns', 'customToolbar'];
99

100
const hasToolbarItem = (options, title) => {
101
  options.title = title;
102

103
  return !isUndefined(find(TOOLBAR_ITEMS, i => options[i]));
104
};
105

106
// Select Toolbar Placement options
107
const STP = {
108
  REPLACE: 'replace',
109
  ABOVE: 'above',
110
  NONE: 'none',
111
  ALWAYS: 'always'
112
};
113

114
class MUIDataTable extends React.Component {
115
  static propTypes = {
116
    /** Title of the table */
117
    title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
118
    /** Data used to describe table */
119
    data: PropTypes.array.isRequired,
120
    /** Columns used to describe table */
121
    columns: PropTypes.PropTypes.arrayOf(
122
      PropTypes.oneOfType([
123
        PropTypes.string,
124
        PropTypes.shape({
125
          label: PropTypes.string,
126
          name: PropTypes.string.isRequired,
127
          options: PropTypes.shape({
128
            display: PropTypes.oneOf(['true', 'false', 'excluded', 'always', true, false]),
129
            empty: PropTypes.bool,
130
            filter: PropTypes.bool,
131
            sort: PropTypes.bool,
132
            print: PropTypes.bool,
28✔
133
            searchable: PropTypes.bool,
28✔
134
            download: PropTypes.bool,
28✔
135
            viewColumns: PropTypes.bool,
28✔
136
            filterList: PropTypes.array,
137
            filterOptions: PropTypes.oneOfType([
138
              PropTypes.array,
139
              PropTypes.shape({
28✔
140
                names: PropTypes.array,
141
                logic: PropTypes.func,
142
                display: PropTypes.func,
143
              }),
28✔
144
            ]),
145
            filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']),
146
            customHeadRender: PropTypes.func,
147
            customBodyRender: PropTypes.func,
1!
148
            customBodyRenderLite: PropTypes.func,
×
149
            customHeadLabelRender: PropTypes.func,
150
            customFilterListOptions: PropTypes.oneOfType([
151
              PropTypes.shape({
152
                render: PropTypes.func,
153
                update: PropTypes.func,
28✔
154
              }),
28✔
155
            ]),
28✔
156
            customFilterListRender: PropTypes.func,
157
            setCellProps: PropTypes.func,
158
            setCellHeaderProps: PropTypes.func,
159
            sortThirdClickReset: PropTypes.bool,
160
            sortDescFirst: PropTypes.bool,
161
          }),
162
        }),
28✔
163
      ]),
164
    ).isRequired,
165
    /** Options used to describe table */
166
    options: PropTypes.shape({
167
      caseSensitive: PropTypes.bool,
168
      columnOrder: PropTypes.array,
169
      count: PropTypes.number,
170
      confirmFilters: PropTypes.bool,
171
      consoleWarnings: PropTypes.bool,
172
      customFilterDialogFooter: PropTypes.func,
173
      customFooter: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
174
      customRowRender: PropTypes.func,
175
      customSearch: PropTypes.func,
176
      customSearchRender: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
177
      customSort: PropTypes.func,
178
      customToolbar: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
179
      customToolbarSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
180
      draggableColumns: PropTypes.object,
181
      enableNestedDataAccess: PropTypes.string,
182
      expandableRows: PropTypes.bool,
183
      expandableRowsHeader: PropTypes.bool,
184
      expandableRowsOnClick: PropTypes.bool,
185
      disableToolbarSelect: PropTypes.bool,
186
      download: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']),
187
      downloadOptions: PropTypes.shape({
188
        filename: PropTypes.string,
28✔
189
        separator: PropTypes.string,
190
        filterOptions: PropTypes.shape({
191
          useDisplayedColumnsOnly: PropTypes.bool,
192
          useDisplayedRowsOnly: PropTypes.bool,
28!
193
        }),
×
194
      }),
195
      filter: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']),
196
      filterArrayFullMatch: PropTypes.bool,
197
      filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']),
198
      fixedHeader: PropTypes.bool,
17✔
199
      fixedSelectColumn: PropTypes.bool,
2✔
200
      getTextLabels: PropTypes.func,
201
      isRowExpandable: PropTypes.func,
202
      isRowSelectable: PropTypes.func,
203
      jumpToPage: PropTypes.bool,
204
      onDownload: PropTypes.func,
28✔
205
      onFilterChange: PropTypes.func,
28✔
206
      onFilterChipClose: PropTypes.func,
140✔
207
      onFilterConfirm: PropTypes.func,
56✔
208
      onFilterDialogOpen: PropTypes.func,
209
      onFilterDialogClose: PropTypes.func,
140✔
210
      onRowClick: PropTypes.func,
211
      onRowsExpand: PropTypes.func,
212
      onRowExpansionChange: PropTypes.func,
28✔
213
      onRowsSelect: PropTypes.func,
28✔
214
      onRowSelectionChange: PropTypes.func,
215
      onTableChange: PropTypes.func,
216
      onTableInit: PropTypes.func,
217
      page: PropTypes.number,
25✔
218
      pagination: PropTypes.bool,
219
      print: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']),
220
      searchProps: PropTypes.object,
221
      selectableRows: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['none', 'single', 'multiple'])]),
222
      selectableRowsHeader: PropTypes.bool,
223
      selectableRowsHideCheckboxes: PropTypes.bool,
224
      selectableRowsOnClick: PropTypes.bool,
225
      serverSide: PropTypes.bool,
28✔
226
      tableId: PropTypes.string,
28✔
227
      tableBodyHeight: PropTypes.string,
28✔
228
      tableBodyMaxHeight: PropTypes.string,
229
      renderExpandableRow: PropTypes.func,
28!
230
      resizableColumns: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
×
231
      responsive: PropTypes.oneOf(['standard', 'vertical', 'verticalAlways', 'simple']),
×
232
      rowHover: PropTypes.bool,
233
      rowsExpanded: PropTypes.array,
234
      rowsPerPage: PropTypes.number,
28✔
235
      rowsPerPageOptions: PropTypes.array,
112✔
236
      rowsSelected: PropTypes.array,
237
      search: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']),
238
      searchOpen: PropTypes.bool,
239
      searchAlwaysOpen: PropTypes.bool,
240
      searchPlaceholder: PropTypes.string,
241
      searchText: PropTypes.string,
242
      setFilterChipProps: PropTypes.func,
243
      setRowProps: PropTypes.func,
112✔
244
      selectToolbarPlacement: PropTypes.oneOfType([
84!
245
        PropTypes.bool,
×
246
        PropTypes.oneOf([STP.REPLACE, STP.ABOVE, STP.NONE, STP.ALWAYS]),
247
      ]),
248
      setTableProps: PropTypes.func,
84✔
249
      sort: PropTypes.bool,
250
      sortOrder: PropTypes.object,
251
      storageKey: PropTypes.string,
84✔
252
      viewColumns: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']),
253
    }),
254
    /** Pass and use className to style MUIDataTable as desired */
28✔
255
    className: PropTypes.string,
256
    components: PropTypes.objectOf(PropTypes.any),
257
  };
112✔
258

259
  static defaultProps = {
112✔
260
    title: '',
112✔
261
    options: {},
262
    data: [],
263
    columns: [],
28✔
264
    components: {
265
      TableBody: DefaultTableBody,
266
      TableFilter: DefaultTableFilter,
28✔
267
      TableFilterList: DefaultTableFilterList,
28✔
268
      TableFooter: DefaultTableFooter,
269
      TableHead: DefaultTableHead,
28✔
270
      TableResize: DefaultTableResize,
28✔
271
      TableToolbar: DefaultTableToolbar,
272
      TableToolbarSelect: DefaultTableToolbarSelect,
28✔
273
      Tooltip: MuiTooltip,
112✔
274
      icons: {},
448!
275
    },
276
  };
448✔
277

112✔
278
  constructor(props) {
112!
279
    super(props);
112!
280
    this.tableRef = React.createRef();
281
    this.tableContent = React.createRef();
282
    this.draggableHeadCellRefs = {};
283
    this.resizeHeadCellRefs = {};
448✔
284
    this.timers = {};
224✔
285
    this.setHeadResizeable = () => {};
224✔
286
    this.updateDividers = () => {};
287

224✔
288
    let defaultState = {
112✔
289
      activeColumn: null,
112!
290
      announceText: null,
112✔
291
      count: 0,
292
      columns: [],
293
      expandedRows: {
294
        data: [],
448✔
295
        lookup: {},
296
      },
297
      data: [],
112!
298
      displayData: [],
112✔
299
      filterData: [],
112✔
300
      filterList: [],
301
      page: 0,
302
      previousSelectedRow: null,
303
      rowsPerPage: 10,
28!
304
      searchProps: {},
305
      searchText: null,
28!
306
      selectedRows: {
×
307
        data: [],
308
        lookup: {},
309
      },
28✔
310
      showResponsive: false,
311
      sortOrder: {},
312
    };
313

314
    this.mergeDefaultOptions(props);
28!
315

28!
316
    const restoredState = load(props.options.storageKey);
×
317
    this.state = Object.assign(defaultState, restoredState ? restoredState : this.getInitTableOptions());
×
318

×
319
    this.setTableData = this.setTableData.bind(this);
320

321
    this.setTableData(props, TABLE_LOAD.INITIAL, true, null, true);
322
  }
323

324
  componentDidMount() {
28✔
325
    this.setHeadResizeable(this.resizeHeadCellRefs, this.tableRef);
28✔
326

327
    // When we have a search, we must reset page to view it unless on serverSide since paging is handled by the user.
328
    if (this.props.options.searchText && !this.props.options.serverSide) this.setState({ page: 0 });
329

330
    this.setTableInit('tableInitialized');
331
  }
332

333
  componentDidUpdate(prevProps) {
334
    if (
335
      this.props.data !== prevProps.data ||
336
      this.props.columns !== prevProps.columns ||
337
      this.props.options !== prevProps.options
338
    ) {
339
      this.updateOptions(this.options, this.props);
340

341
      var didDataUpdate = this.props.data !== prevProps.data;
160✔
342
      if (this.props.data && prevProps.data) {
160✔
343
        didDataUpdate = didDataUpdate && this.props.data.length === prevProps.data.length;
160✔
344
      }
345

160✔
346
      this.setTableData(this.props, TABLE_LOAD.INITIAL, didDataUpdate, () => {
640✔
347
        this.setTableAction('propsUpdate');
640✔
348
      });
349
    }
640✔
350

312✔
351
    if (this.props.options.searchText !== prevProps.options.searchText && !this.props.options.serverSide) {
352
      // When we have a search, we must reset page to view it unless on serverSide since paging is handled by the user.
353
      this.setState({ page: 0 });
354
    }
355

356
    if (
312✔
357
      this.options.resizableColumns === true ||
358
      (this.options.resizableColumns && this.options.resizableColumns.enabled)
359
    ) {
360
      this.setHeadResizeable(this.resizeHeadCellRefs, this.tableRef);
361
      this.updateDividers();
312✔
362
    }
363
  }
364

312✔
365
  updateOptions(options, props) {
312✔
366
    // set backwards compatibility options
367
    if (props.options.disableToolbarSelect === true && props.options.selectToolbarPlacement === undefined) {
468!
368
      // if deprecated option disableToolbarSelect is set and selectToolbarPlacement is default then use it
369
      props.options.selectToolbarPlacement = STP.NONE;
370
    }
371

372
    // provide default tableId when no tableId has been passed as prop
640✔
373
    if (!props.options.tableId) {
374
      props.options.tableId = (Math.random() + '').replace(/\./, '');
640✔
375
    }
20✔
376

377
    this.options = assignwith(options, props.options, (objValue, srcValue, key) => {
378
      // Merge any default options that are objects, as they will be overwritten otherwise
640!
379
      if (key === 'textLabels' || key === 'downloadOptions') return merge(objValue, srcValue);
380
      return;
640✔
381
    });
16✔
382

16✔
383
    this.handleOptionDeprecation(props);
384
  }
16!
385

16✔
386
  getDefaultOptions = () => ({
16✔
387
    caseSensitive: false,
388
    consoleWarnings: true,
389
    disableToolbarSelect: false,
16✔
390
    download: true,
1✔
391
    downloadOptions: {
392
      filename: 'tableDownload.csv',
393
      separator: ',',
394
    },
395
    draggableColumns: {
160✔
396
      enabled: false,
137✔
397
      transitionTime: 300,
398
    },
399
    elevation: 4,
400
    enableNestedDataAccess: '',
1✔
401
    expandableRows: false,
1✔
402
    expandableRowsHeader: true,
1✔
403
    expandableRowsOnClick: false,
404
    filter: true,
1✔
405
    filterArrayFullMatch: true,
1✔
406
    filterType: 'dropdown',
407
    fixedHeader: true,
408
    fixedSelectColumn: true,
1!
409
    pagination: true,
410
    print: true,
411
    resizableColumns: false,
412
    responsive: 'vertical',
1✔
413
    rowHover: true,
1✔
414
    //rowsPerPage: 10,
415
    rowsPerPageOptions: [10, 15, 100],
1✔
416
    search: true,
417
    selectableRows: 'multiple',
1!
418
    selectableRowsHideCheckboxes: false,
1✔
419
    selectableRowsOnClick: false,
1✔
420
    selectableRowsHeader: true,
421
    serverSide: false,
422
    serverSideFilterList: null,
1✔
423
    setTableProps: () => ({}),
424
    sort: true,
425
    sortFilterList: true,
426
    tableBodyHeight: 'auto',
427
    tableBodyMaxHeight: null, // if set, it will override tableBodyHeight
428
    sortOrder: {},
429
    textLabels: getTextLabels(),
430
    viewColumns: true,
431
    selectToolbarPlacement: STP.REPLACE,
537✔
432
  });
433

537✔
434
  warnDep = (msg, consoleWarnings) => {
435
    warnDeprecated(msg, this.options.consoleWarnings);
436
  };
437

438
  warnInfo = (msg, consoleWarnings) => {
439
    warnInfo(msg, this.options.consoleWarnings);
440
  };
441

442
  handleOptionDeprecation = props => {
443
    if (typeof this.options.selectableRows === 'boolean') {
444
      this.warnDep(
40✔
445
        'Using a boolean for selectableRows has been deprecated. Please use string option: multiple | single | none',
446
      );
40✔
447
      this.options.selectableRows = this.options.selectableRows ? 'multiple' : 'none';
160✔
448
    }
160✔
449
    if (['standard', 'vertical', 'verticalAlways', 'simple'].indexOf(this.options.responsive) === -1) {
450
      if (
160✔
451
        [
137✔
452
          'scrollMaxHeight',
453
          'scrollFullHeight',
454
          'stacked',
455
          'stackedFullWidth',
456
          'scrollFullHeightFullWidth',
457
          'scroll',
458
        ].indexOf(this.options.responsive) !== -1
40✔
459
      ) {
460
        this.warnDep(
461
          this.options.responsive +
462
            ' has been deprecated, but will still work in version 3.x. Please use string option: standard | vertical | simple. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
1✔
463
        );
464
      } else {
1✔
465
        this.warnInfo(
1!
466
          this.options.responsive +
1✔
467
            ' is not recognized as a valid input for responsive option. Please use string option: standard | vertical | simple. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
468
        );
469
      }
470
    }
471
    if (this.options.onRowsSelect) {
1✔
472
      this.warnDep(
1!
473
        'onRowsSelect has been renamed onRowSelectionChange. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
×
474
      );
475
    }
×
476
    if (this.options.onRowsExpand) {
477
      this.warnDep(
478
        'onRowsExpand has been renamed onRowExpansionChange. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
479
      );
480
    }
481
    if (this.options.fixedHeaderOptions) {
482
      if (
483
        typeof this.options.fixedHeaderOptions.yAxis !== 'undefined' &&
1!
484
        typeof this.options.fixedHeader === 'undefined'
485
      ) {
486
        this.options.fixedHeader = this.options.fixedHeaderOptions.yAxis;
487
      }
1✔
488
      if (
489
        typeof this.options.fixedHeaderOptions.xAxis !== 'undefined' &&
1✔
490
        typeof this.options.fixedSelectColumn === 'undefined'
1✔
491
      ) {
1✔
492
        this.options.fixedSelectColumn = this.options.fixedHeaderOptions.xAxis;
493
      }
1✔
494
      this.warnDep(
4✔
495
        'fixedHeaderOptions will still work but has been deprecated in favor of fixedHeader and fixedSelectColumn. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
3✔
496
      );
497
    }
1!
498
    if (this.options.serverSideFilterList) {
499
      this.warnDep(
500
        'serverSideFilterList will still work but has been deprecated in favor of the confirmFilters option. See this example for details: https://github.com/gregnb/mui-datatables/blob/master/examples/serverside-filters/index.js More info here: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
501
      );
1✔
502
    }
1✔
503

504
    props.columns.map(c => {
1✔
505
      if (c.options && c.options.customFilterListRender) {
506
        this.warnDep(
507
          'The customFilterListRender option has been deprecated. It is being replaced by customFilterListOptions.render (Specify customFilterListOptions: { render: Function } in column options.)',
508
        );
509
      }
510
    });
1!
511

×
512
    if (this.options.disableToolbarSelect === true) {
513
      this.warnDep(
514
        'disableToolbarSelect has been deprecated but will still work in version 3.x. It is being replaced by "selectToolbarPlacement"="none". More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
515
      );
516
    }
517

518
    // only give this warning message in newer browsers
1✔
519
    if (Object.values) {
520
      if (Object.values(STP).indexOf(this.options.selectToolbarPlacement) === -1) {
1✔
521
        this.warnDep(
522
          'Invalid option value for selectToolbarPlacement. Please check the documentation: https://github.com/gregnb/mui-datatables#options',
523
        );
524
      }
525
    }
526
  };
527

528
  /*
1✔
529
   * React currently does not support deep merge for defaultProps. Objects are overwritten
530
   */
531
  mergeDefaultOptions(props) {
1✔
532
    const defaultOptions = this.getDefaultOptions();
1!
533
    const theProps = Object.assign({}, props);
×
534
    theProps.options = theProps.options || {};
535

536
    this.updateOptions(defaultOptions, theProps);
537
  }
538

539
  validateOptions(options) {
540
    if (options.serverSide && options.onTableChange === undefined) {
541
      throw Error('onTableChange callback must be provided when using serverSide option');
542
    }
543
    if (options.expandableRows && options.renderExpandableRow === undefined) {
1✔
544
      throw Error('renderExpandableRow must be provided when using expandableRows option');
545
    }
1✔
546
    if (options.rowsSelected && Array.isArray(options.rowsSelected) && options.rowsSelected.some(isNaN)) {
1!
547
      warnInfo('When using the rowsSelected option, must be provided an array of numbers only.');
1✔
548
    }
1✔
549
  }
550

551
  setTableAction = action => {
552
    if (typeof this.options.onTableChange === 'function') {
553
      this.options.onTableChange(action, this.state);
554
    }
1✔
555
    if (this.options.storageKey) {
1!
556
      save(this.options.storageKey, this.state);
×
557
    }
558
  };
559

560
  setTableInit = action => {
561
    if (typeof this.options.onTableInit === 'function') {
562
      this.options.onTableInit(action, this.state);
563
    }
1✔
564
  };
1✔
565

566
  getInitTableOptions() {
567
    const optionNames = ['rowsPerPage', 'page', 'rowsSelected', 'rowsPerPageOptions'];
568
    const optState = optionNames.reduce((acc, cur) => {
1✔
569
      if (this.options[cur] !== undefined) {
1!
570
        acc[cur] = this.options[cur];
×
571
      }
572
      return acc;
573
    }, {});
574

575
    this.validateOptions(optState);
576
    return optState;
577
  }
1✔
578

1✔
579
  setHeadCellRef = (index, pos, el) => {
3!
580
    this.draggableHeadCellRefs[index] = el;
1!
581
    this.resizeHeadCellRefs[pos] = el;
582
  };
583

584
  // must be arrow function on local field to refer to the correct instance when passed around
585
  // assigning it as arrow function in the JSX would cause hard to track re-render errors
1✔
586
  getTableContentRef = () => this.tableContent.current;
587

588
  /*
589
   *  Build the source table data
590
   *
591
   *  newColumns - columns from the options object.
1✔
592
   *  prevColumns - columns object saved onto ths state.
593
   *  newColumnOrder - columnOrder from the options object.
4✔
594
   *  prevColumnOrder - columnOrder object saved onto the state.
595
   */
1✔
596

597
  buildColumns = (newColumns, prevColumns = [], newColumnOrder, prevColumnOrder = []) => {
1!
598
    let columnData = [];
599
    let filterData = [];
600
    let filterList = [];
601
    let columnOrder = [];
602

603
    newColumns.forEach((column, colIndex) => {
1✔
604
      let columnOptions = {
1!
605
        display: 'true',
×
606
        empty: false,
607
        filter: true,
608
        sort: true,
609
        print: true,
610
        searchable: true,
611
        download: true,
612
        viewColumns: true,
7✔
613
        sortCompare: null,
614
        sortThirdClickReset: false,
7✔
615
        sortDescFirst: false,
7✔
616
      };
617

7!
618
      columnOrder.push(colIndex);
619
      const options = { ...column.options };
4✔
620

4✔
621
      if (typeof column === 'object') {
622
        if (options) {
×
623
          if (options.display !== undefined) {
×
624
            options.display = options.display.toString();
625
          }
3✔
626

627
          if (options.sortDirection === null || options.sortDirection) {
628
            this.warnDep(
7✔
629
              'The sortDirection column field has been deprecated. Please use the sortOrder option on the options object. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
630
            );
7!
631
          }
632
        }
633

634
        // remember stored version of display if not overwritten
635
        if (
636
          typeof options.display === 'undefined' &&
7✔
637
          prevColumns[colIndex] &&
7!
638
          prevColumns[colIndex].name === column.name &&
×
639
          prevColumns[colIndex].display
640
        ) {
641
          options.display = prevColumns[colIndex].display;
642
        }
643

644
        columnOptions = {
645
          name: column.name,
×
646
          label: column.label ? column.label : column.name,
647
          ...columnOptions,
×
648
          ...options,
×
649
        };
650
      } else {
×
651
        // remember stored version of display if not overwritten
×
652
        if (prevColumns[colIndex] && prevColumns[colIndex].display) {
653
          options.display = prevColumns[colIndex].display;
654
        }
×
655

656
        columnOptions = { ...columnOptions, ...options, name: column, label: column };
657
      }
658

659
      columnData.push(columnOptions);
660

661
      filterData[colIndex] = [];
662
      filterList[colIndex] = [];
663
    });
664

×
665
    if (Array.isArray(newColumnOrder)) {
666
      columnOrder = newColumnOrder;
667
    } else if (
668
      Array.isArray(prevColumnOrder) &&
669
      Array.isArray(newColumns) &&
670
      Array.isArray(prevColumns) &&
5✔
671
      newColumns.length === prevColumns.length
10✔
672
    ) {
10✔
673
      columnOrder = prevColumnOrder;
674
    }
675

676
    return { columns: columnData, filterData, filterList, columnOrder };
677
  };
4✔
678

2✔
679
  transformData = (columns, data) => {
680
    const { enableNestedDataAccess } = this.options;
2✔
681
    const leaf = (obj, path) =>
2✔
682
      (enableNestedDataAccess ? path.split(enableNestedDataAccess) : path.split()).reduce(
683
        (value, el) => (value ? value[el] : undefined),
2!
684
        obj,
685
      );
686

687
    const transformedData = Array.isArray(data[0])
2✔
688
      ? data.map(row => {
689
          let i = -1;
8✔
690

691
          return columns.map(col => {
2✔
692
            if (!col.empty) i++;
2✔
693
            return col.empty ? undefined : row[i];
694
          });
2!
695
        })
×
696
      : data.map(row => columns.map(col => leaf(row, col.name)));
×
697

698
    return transformedData;
699
  };
2✔
700

701
  setTableData(props, status, dataUpdated, callback = () => {}, fromConstructor = false) {
702
    let tableData = [];
703
    let { columns, filterData, filterList, columnOrder } = this.buildColumns(
704
      props.columns,
705
      this.state.columns,
706
      this.options.columnOrder,
707
      this.state.columnOrder,
708
    );
2✔
709

2!
710
    let sortIndex = null;
×
711
    let sortDirection = 'none';
712
    let tableMeta;
713

714
    let sortOrder;
2!
715
    if (this.options.sortOrder && this.options.sortOrder.direction && this.options.sortOrder.name) {
2✔
716
      sortOrder = Object.assign({}, this.options.sortOrder);
717
    } else {
2✔
718
      sortOrder = Object.assign({}, this.state.sortOrder);
2✔
719

2✔
720
      // if no sortOrder, check and see if there's a sortDirection on one of the columns (deprecation notice for this is given above)
721
      if (!sortOrder.direction) {
2✔
722
        props.columns.forEach((column, colIndex) => {
×
723
          if (column.options && (column.options.sortDirection === 'asc' || column.options.sortDirection === 'desc')) {
×
724
            sortOrder.name = column.name;
×
725
            sortOrder.sortDirection = column.sortDirection;
726
          }
727
        });
728
      }
2!
729
    }
×
730

731
    const data = status === TABLE_LOAD.INITIAL ? this.transformData(columns, props.data) : props.data;
2✔
732
    let searchText = status === TABLE_LOAD.INITIAL ? this.options.searchText : null;
733

734
    if (typeof this.options.searchText === 'undefined' && typeof this.state.searchText !== 'undefined') {
2✔
735
      searchText = this.state.searchText;
736
    }
737

738
    let rowsPerPage = this.state.rowsPerPage;
739
    if (typeof this.options.rowsPerPage === 'number') {
740
      rowsPerPage = this.options.rowsPerPage;
741
    }
742

2✔
743
    let page = this.state.page;
2!
744
    if (typeof this.options.page === 'number') {
×
745
      page = this.options.page;
746
    }
747

748
    columns.forEach((column, colIndex) => {
749
      for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
750
        let value = status === TABLE_LOAD.INITIAL ? data[rowIndex][colIndex] : data[rowIndex].data[colIndex];
751

752
        if (typeof tableData[rowIndex] === 'undefined') {
1✔
753
          tableData.push({
6!
754
            index: status === TABLE_LOAD.INITIAL ? rowIndex : data[rowIndex].index,
6!
755
            data: status === TABLE_LOAD.INITIAL ? data[rowIndex] : data[rowIndex].data,
6✔
756
          });
6!
757
        }
6!
758

759
        if (column.filter !== false) {
760
          if (typeof column.customBodyRender === 'function') {
761
            const rowData = tableData[rowIndex].data;
762
            tableMeta = this.getTableMeta(rowIndex, colIndex, rowData, column, data, this.state, tableData);
763
            const funcResult = column.customBodyRender(value, tableMeta);
1!
764

765
            if (React.isValidElement(funcResult) && funcResult.props.value) {
4✔
766
              value = funcResult.props.value;
767
            } else if (typeof funcResult === 'string') {
768
              value = funcResult;
4!
769
            }
770
          }
771

1!
772
          if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
1✔
773
            // it's extremely rare but possible to create an object without a toString method, ex: var x = Object.create(null);
774
            // so this check has to be made
775
            value = value.toString ? value.toString() : '';
1✔
776
          }
1✔
777

778
          if (filterData[colIndex].indexOf(value) < 0 && !Array.isArray(value)) {
1✔
779
            filterData[colIndex].push(value);
4✔
780
          } else if (Array.isArray(value)) {
4✔
781
            value.forEach(element => {
4!
782
              let elmVal;
×
783
              if ((typeof element === 'object' && element !== null) || typeof element === 'function') {
784
                elmVal = element.toString ? element.toString() : '';
785
              } else {
786
                elmVal = element;
1✔
787
              }
788

789
              if (filterData[colIndex].indexOf(elmVal) < 0) {
790
                filterData[colIndex].push(elmVal);
791
              }
792
            });
793
          }
794
        }
795
      }
796

47✔
797
      if (column.filterOptions) {
798
        if (Array.isArray(column.filterOptions)) {
799
          filterData[colIndex] = cloneDeep(column.filterOptions);
800
          this.warnDep(
801
            'filterOptions must now be an object. see https://github.com/gregnb/mui-datatables/tree/master/examples/customize-filter example',
802
          );
803
        } else if (Array.isArray(column.filterOptions.names)) {
804
          filterData[colIndex] = cloneDeep(column.filterOptions.names);
805
        }
806
      }
807

808
      if (column.filterList) {
809
        filterList[colIndex] = cloneDeep(column.filterList);
47✔
810
      } else if (
811
        this.state.filterList &&
47✔
812
        this.state.filterList[colIndex] &&
813
        this.state.filterList[colIndex].length > 0
47✔
814
      ) {
5✔
815
        filterList[colIndex] = cloneDeep(this.state.filterList[colIndex]);
47✔
816
      }
817

818
      if (this.options.sortFilterList) {
819
        const comparator = getCollatorComparator();
820
        filterData[colIndex].sort(comparator);
821
      }
822

823
      if (column.name === sortOrder.name) {
824
        sortDirection = sortOrder.direction;
825
        sortIndex = colIndex;
826
      }
827
    });
828

829
    let selectedRowsData = {
830
      data: [],
831
      lookup: {},
832
    };
×
833

834
    let expandedRowsData = {
835
      data: [],
836
      lookup: {},
837
    };
838

839
    if (TABLE_LOAD.INITIAL) {
840
      // Multiple row selection customization
47!
841
      if (this.options.rowsSelected && this.options.rowsSelected.length && this.options.selectableRows === 'multiple') {
47!
842
        this.options.rowsSelected
×
843
          .filter(selectedRowIndex => selectedRowIndex === 0 || (Number(selectedRowIndex) && selectedRowIndex > 0))
844
          .forEach(row => {
5✔
845
            let rowPos = row;
846

847
            for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) {
848
              if (this.state.displayData[cIndex].dataIndex === row) {
849
                rowPos = cIndex;
850
                break;
851
              }
852
            }
853

854
            selectedRowsData.data.push({ index: rowPos, dataIndex: row });
3✔
855
            selectedRowsData.lookup[row] = true;
856
          });
857

858
        // Single row selection customization
859
      } else if (
860
        this.options.rowsSelected &&
861
        this.options.rowsSelected.length === 1 &&
862
        this.options.selectableRows === 'single'
863
      ) {
864
        let rowPos = this.options.rowsSelected[0];
865

866
        for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) {
867
          if (this.state.displayData[cIndex].dataIndex === this.options.rowsSelected[0]) {
868
            rowPos = cIndex;
869
            break;
870
          }
871
        }
872

873
        selectedRowsData.data.push({ index: rowPos, dataIndex: this.options.rowsSelected[0] });
874
        selectedRowsData.lookup[this.options.rowsSelected[0]] = true;
875
      } else if (
876
        this.options.rowsSelected &&
47!
877
        this.options.rowsSelected.length > 1 &&
878
        this.options.selectableRows === 'single'
93✔
879
      ) {
880
        console.error(
881
          'Multiple values provided for selectableRows, but selectableRows set to "single". Either supply only a single value or use "multiple".',
882
        );
883
      } else if (typeof this.options.rowsSelected === 'undefined' && dataUpdated === false) {
884
        if (this.state.selectedRows) {
885
          selectedRowsData = Object.assign({}, this.state.selectedRows);
886
        }
887
      }
888

889
      if (this.options.rowsExpanded && this.options.rowsExpanded.length && this.options.expandableRows) {
890
        this.options.rowsExpanded.forEach(row => {
5✔
891
          let rowPos = row;
892

893
          for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) {
894
            if (this.state.displayData[cIndex].dataIndex === row) {
895
              rowPos = cIndex;
896
              break;
897
            }
898
          }
899

900
          expandedRowsData.data.push({ index: rowPos, dataIndex: row });
901
          expandedRowsData.lookup[row] = true;
902
        });
903
      } else if (typeof this.options.rowsExpanded === 'undefined' && dataUpdated === false && this.state.expandedRows) {
904
        expandedRowsData = Object.assign({}, this.state.expandedRows);
905
      }
906
    }
907

908
    if (!this.options.serverSide && sortIndex !== null) {
909
      const sortedData = this.sortTable(tableData, sortIndex, sortDirection, columns[sortIndex].sortCompare);
910
      tableData = sortedData.data;
911
    }
912

913
    /* set source data and display Data set source set */
914
    let stateUpdates = {
915
      columns: columns,
916
      filterData: filterData,
917
      filterList: filterList,
918
      searchText: searchText,
919
      selectedRows: selectedRowsData,
920
      expandedRows: expandedRowsData,
921
      count: this.options.count,
922
      data: tableData,
923
      sortOrder: sortOrder,
924
      rowsPerPage,
925
      page,
926
      displayData: this.getDisplayData(columns, tableData, filterList, searchText, tableMeta, props),
927
      columnOrder,
928
    };
929

930
    if (fromConstructor) {
931
      this.state = Object.assign({}, this.state, stateUpdates);
932
    } else {
933
      this.setState(stateUpdates, callback);
934
    }
935
  }
936

937
  /*
938
   *  Build the table data used to display to the user (ie: after filter/search applied)
939
   */
940
  computeDisplayRow(
941
    columns,
942
    row,
943
    rowIndex,
944
    filterList,
945
    searchText,
946
    dataForTableMeta,
947
    options,
948
    props,
949
    currentTableData,
950
  ) {
951
    let isFiltered = false;
952
    let isSearchFound = false;
953
    let displayRow = [];
954

955
    for (let index = 0; index < row.length; index++) {
956
      let columnDisplay = row[index];
957
      let columnValue = row[index];
958
      let column = columns[index];
959

960
      if (column.customBodyRenderLite) {
961
        displayRow.push(column.customBodyRenderLite);
962
      } else if (column.customBodyRender) {
963
        const tableMeta = this.getTableMeta(
964
          rowIndex,
965
          index,
966
          row,
967
          column,
968
          dataForTableMeta,
969
          {
970
            ...this.state,
971
            filterList: filterList,
972
            searchText: searchText,
973
          },
974
          currentTableData,
975
        );
976

977
        const funcResult = column.customBodyRender(
978
          columnValue,
979
          tableMeta,
980
          this.updateDataCol.bind(null, rowIndex, index),
981
        );
982
        columnDisplay = funcResult;
983

984
        /* drill down to get the value of a cell */
985
        columnValue =
986
          typeof funcResult === 'string' || !funcResult
987
            ? funcResult
988
            : funcResult.props && funcResult.props.value
989
            ? funcResult.props.value
990
            : columnValue;
991

992
        displayRow.push(columnDisplay);
993
      } else {
994
        displayRow.push(columnDisplay);
995
      }
996

997
      const columnVal = columnValue === null || columnValue === undefined ? '' : columnValue.toString();
998

999
      const filterVal = filterList[index];
1000
      const caseSensitive = options.caseSensitive;
1001
      const filterType = column.filterType || options.filterType;
1002
      if (filterVal.length || filterType === 'custom') {
1003
        if (column.filterOptions && column.filterOptions.logic) {
1004
          if (column.filterOptions.logic(columnValue, filterVal, row)) isFiltered = true;
1005
        } else if (filterType === 'textField' && !this.hasSearchText(columnVal, filterVal, caseSensitive)) {
1006
          isFiltered = true;
1007
        } else if (
1008
          filterType !== 'textField' &&
1009
          Array.isArray(columnValue) === false &&
1010
          filterVal.indexOf(columnValue) < 0
1011
        ) {
1012
          isFiltered = true;
1013
        } else if (filterType !== 'textField' && Array.isArray(columnValue)) {
1014
          if (options.filterArrayFullMatch) {
1015
            //true if every filterVal exists in columnVal, false otherwise
1016
            const isFullMatch = filterVal.every(el => {
1017
              return columnValue.indexOf(el) >= 0;
1018
            });
1019
            //if it is not a fullMatch, filter row out
1020
            if (!isFullMatch) {
1021
              isFiltered = true;
1022
            }
1023
          } else {
1024
            const isAnyMatch = filterVal.some(el => {
1025
              return columnValue.indexOf(el) >= 0;
1026
            });
1027
            //if no value matches, filter row out
1028
            if (!isAnyMatch) {
1029
              isFiltered = true;
1030
            }
1031
          }
1032
        }
1033
      }
1034

1035
      if (
1036
        searchText &&
1037
        column.display !== 'excluded' &&
1038
        this.hasSearchText(columnVal, searchText, caseSensitive) &&
1039
        column.display !== 'false' &&
1040
        column.searchable
1041
      ) {
1042
        isSearchFound = true;
1043
      }
1044
    }
1045

1046
    const { customSearch } = props.options;
1047

1048
    if (searchText && customSearch) {
1049
      const customSearchResult = customSearch(searchText, row, columns);
1050
      if (typeof customSearchResult !== 'boolean') {
1051
        console.error('customSearch must return a boolean');
1052
      } else {
1053
        isSearchFound = customSearchResult;
1054
      }
1055
    }
1056

1057
    if (options.serverSide) {
1058
      if (customSearch) {
1059
        console.warn('Server-side filtering is enabled, hence custom search will be ignored.');
1060
      }
1061

1062
      return displayRow;
1063
    }
1064

1065
    if (isFiltered || (searchText && !isSearchFound)) return null;
1066
    else return displayRow;
1067
  }
1068

1069
  hasSearchText = (toSearch, toFind, caseSensitive) => {
1070
    let stack = toSearch.toString();
1071
    let needle = toFind.toString();
1072

1073
    if (!caseSensitive) {
1074
      needle = needle.toLowerCase();
1075
      stack = stack.toLowerCase();
1076
    }
1077

1078
    return stack.indexOf(needle) >= 0;
1079
  };
1080

1081
  updateDataCol = (row, index, value) => {
1082
    this.setState(prevState => {
1083
      let changedData = cloneDeep(prevState.data);
1084
      let filterData = cloneDeep(prevState.filterData);
1085

1086
      const tableMeta = this.getTableMeta(
1087
        row,
1088
        index,
1089
        row,
1090
        prevState.columns[index],
1091
        prevState.data,
1092
        prevState,
1093
        prevState.data,
1094
      );
1095
      const funcResult = prevState.columns[index].customBodyRender(value, tableMeta);
1096

1097
      const filterValue =
1098
        React.isValidElement(funcResult) && funcResult.props.value
1099
          ? funcResult.props.value
1100
          : prevState['data'][row][index];
1101

1102
      const prevFilterIndex = filterData[index].indexOf(filterValue);
1103
      filterData[index].splice(prevFilterIndex, 1, filterValue);
1104

1105
      changedData[row].data[index] = value;
1106

1107
      if (this.options.sortFilterList) {
1108
        const comparator = getCollatorComparator();
1109
        filterData[index].sort(comparator);
1110
      }
1111

1112
      return {
1113
        data: changedData,
1114
        filterData: filterData,
1115
        displayData: this.getDisplayData(
1116
          prevState.columns,
1117
          changedData,
1118
          prevState.filterList,
1119
          prevState.searchText,
1120
          null,
1121
          this.props,
1122
        ),
1123
      };
1124
    });
1125
  };
1126

1127
  getTableMeta = (rowIndex, colIndex, rowData, columnData, tableData, curState, currentTableData) => {
1128
    const { columns, data, displayData, filterData, ...tableState } = curState;
1129

1130
    return {
1131
      rowIndex: rowIndex,
1132
      columnIndex: colIndex,
1133
      columnData: columnData,
1134
      rowData: rowData,
1135
      tableData: tableData,
1136
      tableState: tableState,
1137
      currentTableData: currentTableData,
1138
    };
1139
  };
1140

1141
  getDisplayData(columns, data, filterList, searchText, tableMeta, props) {
1142
    let newRows = [];
1143
    const dataForTableMeta = tableMeta ? tableMeta.tableData : props.data;
1144

1145
    for (let index = 0; index < data.length; index++) {
1146
      const value = data[index].data;
1147
      const displayRow = this.computeDisplayRow(
1148
        columns,
1149
        value,
1150
        index,
1151
        filterList,
1152
        searchText,
1153
        dataForTableMeta,
1154
        this.options,
1155
        props,
1156
        data,
1157
      );
1158

1159
      if (displayRow) {
1160
        newRows.push({
1161
          data: displayRow,
1162
          dataIndex: data[index].index,
1163
        });
1164
      }
1165
    }
1166
    return newRows;
1167
  }
1168

1169
  toggleViewColumn = index => {
1170
    this.setState(
1171
      prevState => {
1172
        const columns = cloneDeep(prevState.columns);
1173
        columns[index].display = columns[index].display === 'true' ? 'false' : 'true';
1174
        return {
1175
          columns: columns,
1176
        };
1177
      },
1178
      () => {
1179
        this.setTableAction('viewColumnsChange');
1180
        var cb = this.options.onViewColumnsChange || this.options.onColumnViewChange;
1181

1182
        if (cb) {
1183
          cb(this.state.columns[index].name, this.state.columns[index].display === 'true' ? 'add' : 'remove');
1184
        }
1185
      },
1186
    );
1187
  };
1188

1189
  updateColumns = newColumns => {
1190
    this.setState(
1191
      prevState => {
1192
        return {
1193
          columns: newColumns,
1194
        };
1195
      },
1196
      () => {
1197
        this.setTableAction('viewColumnsChange');
1198
        var cb = this.options.onViewColumnsChange || this.options.onColumnViewChange;
1199

1200
        if (cb) {
1201
          cb(null, 'update', newColumns);
1202
        }
1203
      },
1204
    );
1205
  };
1206

1207
  getSortDirectionLabel(sortOrder) {
1208
    switch (sortOrder.direction) {
1209
      case 'asc':
1210
        return 'ascending';
1211
      case 'desc':
1212
        return 'descending';
1213
      case 'none':
1214
        return 'none';
1215
      default:
1216
        return '';
1217
    }
1218
  }
1219

1220
  getTableProps() {
1221
    const { classes } = this.props;
1222
    const tableProps = this.options.setTableProps() || {};
1223

1224
    tableProps.className = clsx(classes.tableRoot, tableProps.className);
1225

1226
    return tableProps;
1227
  }
1228

1229
  toggleSortColumn = index => {
1230
    this.setState(
1231
      prevState => {
1232
        let columns = cloneDeep(prevState.columns);
1233
        let data = prevState.data;
1234
        let newOrder = columns[index].sortDescFirst ? 'desc' : 'asc'; // default
1235

1236
        let sequenceOrder = ['asc', 'desc'];
1237
        if (columns[index].sortDescFirst) {
1238
          sequenceOrder = ['desc', 'asc'];
1239
        }
1240
        if (columns[index].sortThirdClickReset) {
1241
          sequenceOrder.push('none');
1242
        }
1243

1244
        if (columns[index].name === this.state.sortOrder.name) {
1245
          let pos = sequenceOrder.indexOf(this.state.sortOrder.direction);
1246
          if (pos !== -1) {
1247
            pos++;
1248
            if (pos >= sequenceOrder.length) pos = 0;
1249
            newOrder = sequenceOrder[pos];
1250
          }
1251
        }
1252

1253
        const newSortOrder = {
1254
          name: columns[index].name,
1255
          direction: newOrder,
1256
        };
1257

1258
        const orderLabel = this.getSortDirectionLabel(newSortOrder);
1259
        const announceText = `Table now sorted by ${columns[index].name} : ${orderLabel}`;
1260

1261
        let newState = {
1262
          columns: columns,
1263
          announceText: announceText,
1264
          activeColumn: index,
1265
        };
1266

1267
        if (this.options.serverSide) {
1268
          newState = {
1269
            ...newState,
1270
            data: prevState.data,
1271
            displayData: prevState.displayData,
1272
            selectedRows: prevState.selectedRows,
1273
            sortOrder: newSortOrder,
1274
          };
1275
        } else {
1276
          const sortedData = this.sortTable(data, index, newOrder, columns[index].sortCompare);
1277

1278
          newState = {
1279
            ...newState,
1280
            data: sortedData.data,
1281
            displayData: this.getDisplayData(
1282
              columns,
1283
              sortedData.data,
1284
              prevState.filterList,
1285
              prevState.searchText,
1286
              null,
1287
              this.props,
1288
            ),
1289
            selectedRows: sortedData.selectedRows,
1290
            sortOrder: newSortOrder,
1291
            previousSelectedRow: null,
1292
          };
1293
        }
1294

1295
        return newState;
1296
      },
1297
      () => {
1298
        this.setTableAction('sort');
1299

1300
        if (this.options.onColumnSortChange) {
1301
          this.options.onColumnSortChange(this.state.sortOrder.name, this.state.sortOrder.direction);
1302
        }
1303
      },
1304
    );
1305
  };
1306

1307
  changeRowsPerPage = rows => {
1308
    const rowCount = this.options.count || this.state.displayData.length;
1309

1310
    this.setState(
1311
      () => ({
1312
        rowsPerPage: rows,
1313
        page: getPageValue(rowCount, rows, this.state.page),
1314
      }),
1315
      () => {
1316
        this.setTableAction('changeRowsPerPage');
1317

1318
        if (this.options.onChangeRowsPerPage) {
1319
          this.options.onChangeRowsPerPage(this.state.rowsPerPage);
1320
        }
1321
      },
1322
    );
1323
  };
1324

1325
  changePage = page => {
1326
    this.setState(
1327
      () => ({
1328
        page: page,
1329
      }),
1330
      () => {
1331
        this.setTableAction('changePage');
1332
        if (this.options.onChangePage) {
1333
          this.options.onChangePage(this.state.page);
1334
        }
1335
      },
1336
    );
1337
  };
1338

1339
  searchClose = () => {
1340
    this.setState(
1341
      prevState => ({
1342
        searchText: null,
1343
        displayData: this.options.serverSide
1344
          ? prevState.displayData
1345
          : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, null, null, this.props),
1346
      }),
1347
      () => {
1348
        this.setTableAction('search');
1349
        if (this.options.onSearchChange) {
1350
          this.options.onSearchChange(this.state.searchText);
1351
        }
1352
      },
1353
    );
1354
  };
1355

1356
  searchTextUpdate = text => {
1357
    this.setState(
1358
      prevState => ({
1359
        searchText: text && text.length ? text : null,
1360
        page: 0,
1361
        displayData: this.options.serverSide
1362
          ? prevState.displayData
1363
          : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text, null, this.props),
1364
      }),
1365
      () => {
1366
        this.setTableAction('search');
1367
        if (this.options.onSearchChange) {
1368
          this.options.onSearchChange(this.state.searchText);
1369
        }
1370
      },
1371
    );
1372
  };
1373

1374
  resetFilters = () => {
1375
    this.setState(
1376
      prevState => {
1377
        const filterList = prevState.columns.map(() => []);
1378

1379
        return {
1380
          filterList: filterList,
1381
          displayData: this.options.serverSide
1382
            ? prevState.displayData
1383
            : this.getDisplayData(
1384
                prevState.columns,
1385
                prevState.data,
1386
                filterList,
1387
                prevState.searchText,
1388
                null,
1389
                this.props,
1390
              ),
1391
        };
1392
      },
1393
      () => {
1394
        this.setTableAction('resetFilters');
1395
        if (this.options.onFilterChange) {
1396
          this.options.onFilterChange(null, this.state.filterList, 'reset', null);
1397
        }
1398
      },
1399
    );
1400
  };
1401

1402
  updateFilterByType = (filterList, index, value, type, customUpdate) => {
1403
    const filterPos = filterList[index].findIndex(filter => isEqual(filter, value));
1404

1405
    switch (type) {
1406
      case 'checkbox':
1407
        filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(value);
1408
        break;
1409
      case 'chip':
1410
        filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(value);
1411
        break;
1412
      case 'multiselect':
1413
        filterList[index] = value === '' ? [] : value;
1414
        break;
1415
      case 'dropdown':
1416
        filterList[index] = value;
1417
        break;
1418
      case 'custom':
1419
        if (customUpdate) {
1420
          filterList = customUpdate(filterList, filterPos, index);
1421
        } else {
1422
          filterList[index] = value;
1423
        }
1424
        break;
1425
      default:
1426
        filterList[index] = filterPos >= 0 || value === '' ? [] : [value];
1427
    }
1428
  };
1429

1430
  filterUpdate = (index, value, column, type, customUpdate, next) => {
1431
    this.setState(
1432
      prevState => {
1433
        const filterList = cloneDeep(prevState.filterList);
1434
        this.updateFilterByType(filterList, index, value, type, customUpdate);
1435

1436
        return {
1437
          page: 0,
1438
          filterList: filterList,
1439
          displayData: this.options.serverSide
1440
            ? prevState.displayData
1441
            : this.getDisplayData(
1442
                prevState.columns,
1443
                prevState.data,
1444
                filterList,
1445
                prevState.searchText,
1446
                null,
1447
                this.props,
1448
              ),
1449
          previousSelectedRow: null,
1450
        };
1451
      },
1452
      () => {
1453
        this.setTableAction('filterChange');
1454
        if (this.options.onFilterChange) {
1455
          this.options.onFilterChange(column, this.state.filterList, type, index, this.state.displayData);
1456
        }
1457
        next && next(this.state.filterList);
1458
      },
1459
    );
1460
  };
1461

1462
  // Collapses or expands all expanded rows
1463
  toggleAllExpandableRows = () => {
1464
    let expandedRowsData = [...this.state.expandedRows.data];
1465
    const { isRowExpandable } = this.options;
1466
    let affecttedRows = [];
1467

1468
    if (expandedRowsData.length > 0) {
1469
      // collapse all
1470
      for (let ii = expandedRowsData.length - 1; ii >= 0; ii--) {
1471
        let item = expandedRowsData[ii];
1472
        if (!isRowExpandable || (isRowExpandable && isRowExpandable(item.dataIndex, this.state.expandedRows))) {
1473
          affecttedRows.push(expandedRowsData.splice(ii, 1));
1474
        }
1475
      }
1476
    } else {
1477
      // expand all
1478
      for (let ii = 0; ii < this.state.data.length; ii++) {
1479
        let item = this.state.data[ii];
1480
        if (!isRowExpandable || (isRowExpandable && isRowExpandable(item.dataIndex, this.state.expandedRows))) {
1481
          if (this.state.expandedRows.lookup[item.index] !== true) {
1482
            let newItem = {
1483
              index: ii,
1484
              dataIndex: item.index,
1485
            };
1486
            expandedRowsData.push(newItem);
1487
            affecttedRows.push(newItem);
1488
          }
1489
        }
1490
      }
1491
    }
1492

1493
    this.setState(
1494
      {
1495
        expandedRows: {
1496
          lookup: buildMap(expandedRowsData),
1497
          data: expandedRowsData,
1498
        },
1499
      },
1500
      () => {
1501
        this.setTableAction('expandRow');
1502
        if (this.options.onRowExpansionChange) {
1503
          this.options.onRowExpansionChange(
1504
            affecttedRows,
1505
            this.state.expandedRows.data,
1506
            this.state.expandedRows.data.map(item => item.dataIndex),
1507
          );
1508
        }
1509
      },
1510
    );
1511
  };
1512

1513
  areAllRowsExpanded = () => {
1514
    return this.state.expandedRows.data.length === this.state.data.length;
1515
  };
1516

1517
  updateColumnOrder = (columnOrder, columnIndex, newPosition) => {
1518
    this.setState(
1519
      prevState => {
1520
        return {
1521
          columnOrder,
1522
        };
1523
      },
1524
      () => {
1525
        this.setTableAction('columnOrderChange');
1526
        if (this.options.onColumnOrderChange) {
1527
          this.options.onColumnOrderChange(this.state.columnOrder, columnIndex, newPosition);
1528
        }
1529
      },
1530
    );
1531
  };
1532

1533
  selectRowDelete = () => {
1534
    const { selectedRows, data, filterList } = this.state;
1535

1536
    const selectedMap = buildMap(selectedRows.data);
1537
    const cleanRows = data.filter(({ index }) => !selectedMap[index]);
1538

1539
    if (this.options.onRowsDelete) {
1540
      if (
1541
        this.options.onRowsDelete(
1542
          selectedRows,
1543
          cleanRows.map(ii => ii.data),
1544
        ) === false
1545
      )
1546
        return;
1547
    }
1548

1549
    this.setTableData(
1550
      {
1551
        columns: this.props.columns,
1552
        data: cleanRows,
1553
        options: {
1554
          filterList: filterList,
1555
        },
1556
      },
1557
      TABLE_LOAD.UPDATE,
1558
      true,
1559
      () => {
1560
        this.setTableAction('rowDelete');
1561
      },
1562
    );
1563
  };
1564

1565
  toggleExpandRow = row => {
1566
    const { dataIndex } = row;
1567
    const { isRowExpandable } = this.options;
1568
    let { expandedRows } = this.state;
1569
    const expandedRowsData = [...expandedRows.data];
1570
    let shouldCollapseExpandedRow = false;
1571
    let hasRemovedRow = false;
1572
    let removedRow = [];
1573

1574
    for (var cIndex = 0; cIndex < expandedRowsData.length; cIndex++) {
1575
      if (expandedRowsData[cIndex].dataIndex === dataIndex) {
1576
        shouldCollapseExpandedRow = true;
1577
        break;
1578
      }
1579
    }
1580

1581
    if (shouldCollapseExpandedRow) {
1582
      if ((isRowExpandable && isRowExpandable(dataIndex, expandedRows)) || !isRowExpandable) {
1583
        removedRow = expandedRowsData.splice(cIndex, 1);
1584
        hasRemovedRow = true;
1585
      }
1586
    } else {
1587
      if (isRowExpandable && isRowExpandable(dataIndex, expandedRows)) expandedRowsData.push(row);
1588
      else if (!isRowExpandable) expandedRowsData.push(row);
1589
    }
1590

1591
    this.setState(
1592
      {
1593
        curExpandedRows: hasRemovedRow ? removedRow : [row],
1594
        expandedRows: {
1595
          lookup: buildMap(expandedRowsData),
1596
          data: expandedRowsData,
1597
        },
1598
      },
1599
      () => {
1600
        this.setTableAction('rowExpansionChange');
1601
        if (this.options.onRowExpansionChange || this.options.onRowsExpand) {
1602
          let expandCallback = this.options.onRowExpansionChange || this.options.onRowsExpand;
1603
          expandCallback(this.state.curExpandedRows, this.state.expandedRows.data);
1604
        }
1605
      },
1606
    );
1607
  };
1608

1609
  selectRowUpdate = (type, value, shiftAdjacentRows = []) => {
1610
    // safety check
1611
    const { selectableRows } = this.options;
1612
    if (selectableRows === 'none') {
1613
      return;
1614
    }
1615

1616
    if (type === 'head') {
1617
      const { isRowSelectable } = this.options;
1618
      this.setState(
1619
        prevState => {
1620
          const { displayData, selectedRows: prevSelectedRows } = prevState;
1621
          const selectedRowsLen = prevState.selectedRows.data.length;
1622
          let isDeselect =
1623
            selectedRowsLen === displayData.length || (selectedRowsLen < displayData.length && selectedRowsLen > 0);
1624

1625
          let selectedRows = displayData.reduce((arr, d, i) => {
1626
            const selected = isRowSelectable ? isRowSelectable(displayData[i].dataIndex, prevSelectedRows) : true;
1627
            selected && arr.push({ index: i, dataIndex: displayData[i].dataIndex });
1628
            return arr;
1629
          }, []);
1630

1631
          let newRows = [...selectedRows];
1632
          let selectedMap = buildMap(newRows);
1633

1634
          // if the select toolbar is disabled, the rules are a little different
1635
          if (this.options.selectToolbarPlacement === STP.NONE) {
1636
            if (selectedRowsLen > displayData.length) {
1637
              isDeselect = true;
1638
            } else {
1639
              for (let ii = 0; ii < displayData.length; ii++) {
1640
                if (!selectedMap[displayData[ii].dataIndex]) {
1641
                  isDeselect = true;
1642
                }
1643
              }
1644
            }
1645
          }
1646

1647
          if (isDeselect) {
1648
            newRows = prevState.selectedRows.data.filter(({ dataIndex }) => !selectedMap[dataIndex]);
1649
            selectedMap = buildMap(newRows);
1650
          }
1651

1652
          return {
1653
            curSelectedRows: newRows,
1654
            selectedRows: {
1655
              data: newRows,
1656
              lookup: selectedMap,
1657
            },
1658
            previousSelectedRow: null,
1659
          };
1660
        },
1661
        () => {
1662
          this.setTableAction('rowSelectionChange');
1663
          if (this.options.onRowSelectionChange) {
1664
            this.options.onRowSelectionChange(
1665
              this.state.curSelectedRows,
1666
              this.state.selectedRows.data,
1667
              this.state.selectedRows.data.map(item => item.dataIndex),
1668
            );
1669
          } else if (this.options.onRowsSelect) {
1670
            this.options.onRowsSelect(
1671
              this.state.curSelectedRows,
1672
              this.state.selectedRows.data,
1673
              this.state.selectedRows.data.map(item => item.dataIndex),
1674
            );
1675
          }
1676
        },
1677
      );
1678
    } else if (type === 'cell') {
1679
      this.setState(
1680
        prevState => {
1681
          const { dataIndex } = value;
1682
          let selectedRows = [...prevState.selectedRows.data];
1683
          let rowPos = -1;
1684

1685
          for (let cIndex = 0; cIndex < selectedRows.length; cIndex++) {
1686
            if (selectedRows[cIndex].dataIndex === dataIndex) {
1687
              rowPos = cIndex;
1688
              break;
1689
            }
1690
          }
1691

1692
          if (rowPos >= 0) {
1693
            selectedRows.splice(rowPos, 1);
1694

1695
            // handle rows affected by shift+click
1696
            if (shiftAdjacentRows.length > 0) {
1697
              let shiftAdjacentMap = buildMap(shiftAdjacentRows);
1698
              for (let cIndex = selectedRows.length - 1; cIndex >= 0; cIndex--) {
1699
                if (shiftAdjacentMap[selectedRows[cIndex].dataIndex]) {
1700
                  selectedRows.splice(cIndex, 1);
1701
                }
1702
              }
1703
            }
1704
          } else if (selectableRows === 'single') {
1705
            selectedRows = [value];
1706
          } else {
1707
            // multiple
1708
            selectedRows.push(value);
1709

1710
            // handle rows affected by shift+click
1711
            if (shiftAdjacentRows.length > 0) {
1712
              let selectedMap = buildMap(selectedRows);
1713
              shiftAdjacentRows.forEach(aRow => {
1714
                if (!selectedMap[aRow.dataIndex]) {
1715
                  selectedRows.push(aRow);
1716
                }
1717
              });
1718
            }
1719
          }
1720

1721
          return {
1722
            selectedRows: {
1723
              lookup: buildMap(selectedRows),
1724
              data: selectedRows,
1725
            },
1726
            previousSelectedRow: value,
1727
          };
1728
        },
1729
        () => {
1730
          this.setTableAction('rowSelectionChange');
1731
          if (this.options.onRowSelectionChange) {
1732
            this.options.onRowSelectionChange(
1733
              [value],
1734
              this.state.selectedRows.data,
1735
              this.state.selectedRows.data.map(item => item.dataIndex),
1736
            );
1737
          } else if (this.options.onRowsSelect) {
1738
            this.options.onRowsSelect(
1739
              [value],
1740
              this.state.selectedRows.data,
1741
              this.state.selectedRows.data.map(item => item.dataIndex),
1742
            );
1743
          }
1744
        },
1745
      );
1746
    } else if (type === 'custom') {
1747
      const { displayData } = this.state;
1748

1749
      const data = value.map(row => ({ index: row, dataIndex: displayData[row].dataIndex }));
1750
      const lookup = buildMap(data);
1751

1752
      this.setState(
1753
        {
1754
          selectedRows: { data, lookup },
1755
          previousSelectedRow: null,
1756
        },
1757
        () => {
1758
          this.setTableAction('rowSelectionChange');
1759
          if (this.options.onRowSelectionChange) {
1760
            this.options.onRowSelectionChange(
1761
              this.state.selectedRows.data,
1762
              this.state.selectedRows.data,
1763
              this.state.selectedRows.data.map(item => item.dataIndex),
1764
            );
1765
          } else if (this.options.onRowsSelect) {
1766
            this.options.onRowsSelect(
1767
              this.state.selectedRows.data,
1768
              this.state.selectedRows.data,
1769
              this.state.selectedRows.data.map(item => item.dataIndex),
1770
            );
1771
          }
1772
        },
1773
      );
1774
    }
1775
  };
1776

1777
  sortTable(data, col, order, columnSortCompare = null) {
1778
    let hasCustomTableSort = this.options.customSort && !columnSortCompare;
1779
    let meta = { selectedRows: this.state.selectedRows }; // meta for customSort
1780
    let dataSrc = hasCustomTableSort
1781
      ? this.options.customSort(data, col, order || (this.options.sortDescFirst ? 'desc' : 'asc'), meta)
1782
      : data;
1783

1784
    // reset the order by index
1785
    let noSortData;
1786
    if (order === 'none') {
1787
      noSortData = data.reduce((r, i) => {
1788
        r[i.index] = i;
1789
        return r;
1790
      }, []);
1791
    }
1792

1793
    let sortedData = dataSrc.map((row, sIndex) => ({
1794
      data: row.data[col],
1795
      rowData: row.data,
1796
      position: sIndex,
1797
      rowSelected: this.state.selectedRows.lookup[row.index] ? true : false,
1798
    }));
1799

1800
    if (!hasCustomTableSort) {
1801
      const sortFn = columnSortCompare || sortCompare;
1802
      sortedData.sort(sortFn(order));
1803
    }
1804

1805
    let tableData = [];
1806
    let selectedRows = [];
1807

1808
    for (let i = 0; i < sortedData.length; i++) {
1809
      const row = sortedData[i];
1810
      tableData.push(dataSrc[row.position]);
1811
      if (row.rowSelected) {
1812
        selectedRows.push({ index: i, dataIndex: dataSrc[row.position].index });
1813
      }
1814
    }
1815

1816
    return {
1817
      data: order === 'none' ? noSortData : tableData,
1818
      selectedRows: {
1819
        lookup: buildMap(selectedRows),
1820
        data: selectedRows,
1821
      },
1822
    };
1823
  }
1824

1825
  render() {
1826
    const {
1827
      classes,
1828
      className,
1829
      title,
1830
      components: {
1831
        TableBody,
1832
        TableFilterList,
1833
        TableFooter,
1834
        TableHead,
1835
        TableResize,
1836
        TableToolbar,
1837
        TableToolbarSelect,
1838
        DragDropBackend = HTML5Backend,
1839
      },
1840
    } = this.props;
1841
    const {
1842
      announceText,
1843
      activeColumn,
1844
      data,
1845
      displayData,
1846
      columns,
1847
      page,
1848
      filterData,
1849
      filterList,
1850
      selectedRows,
1851
      previousSelectedRow,
1852
      expandedRows,
1853
      searchText,
1854
      sortOrder,
1855
      serverSideFilterList,
1856
      columnOrder,
1857
    } = this.state;
1858

1859
    const TableBodyComponent = TableBody || DefaultTableBody;
1860
    const TableFilterListComponent = TableFilterList || DefaultTableFilterList;
1861
    const TableFooterComponent = TableFooter || DefaultTableFooter;
1862
    const TableHeadComponent = TableHead || DefaultTableHead;
1863
    const TableResizeComponent = TableResize || DefaultTableResize;
1864
    const TableToolbarComponent = TableToolbar || DefaultTableToolbar;
1865
    const TableToolbarSelectComponent = TableToolbarSelect || DefaultTableToolbarSelect;
1866

1867
    const rowCount = this.state.count || displayData.length;
1868
    const rowsPerPage = this.options.pagination ? this.state.rowsPerPage : displayData.length;
1869
    const showToolbar = hasToolbarItem(this.options, title);
1870
    const columnNames = columns.map(column => ({
1871
      name: column.name,
1872
      filterType: column.filterType || this.options.filterType,
1873
    }));
1874
    const responsiveOption = this.options.responsive;
1875
    let paperClasses = `${classes.paper} ${className}`;
1876
    let maxHeight = this.options.tableBodyMaxHeight;
1877
    let responsiveClass;
1878

1879
    switch (responsiveOption) {
1880
      // deprecated
1881
      case 'scroll':
1882
        responsiveClass = classes.responsiveScroll;
1883
        maxHeight = '499px';
1884
        break;
1885
      // deprecated
1886
      case 'scrollMaxHeight':
1887
        responsiveClass = classes.responsiveScrollMaxHeight;
1888
        maxHeight = '499px';
1889
        break;
1890
      // deprecated
1891
      case 'scrollFullHeight':
1892
        responsiveClass = classes.responsiveScrollFullHeight;
1893
        maxHeight = 'none';
1894
        break;
1895
      // deprecated
1896
      case 'scrollFullHeightFullWidth':
1897
        responsiveClass = classes.responsiveScrollFullHeight;
1898
        paperClasses = `${classes.paperResponsiveScrollFullHeightFullWidth} ${className}`;
1899
        break;
1900
      // deprecated
1901
      case 'stacked':
1902
        responsiveClass = classes.responsiveStacked;
1903
        maxHeight = 'none';
1904
        break;
1905
      // deprecated
1906
      case 'stackedFullWidth':
1907
        responsiveClass = classes.responsiveStackedFullWidth;
1908
        paperClasses = `${classes.paperResponsiveScrollFullHeightFullWidth} ${className}`;
1909
        maxHeight = 'none';
1910
        break;
1911

1912
      default:
1913
        responsiveClass = classes.responsiveBase;
1914
        break;
1915
    }
1916

1917
    var tableHeightVal = {};
1918
    if (maxHeight) {
1919
      tableHeightVal.maxHeight = maxHeight;
1920
    }
1921
    if (this.options.tableBodyHeight) {
1922
      tableHeightVal.height = this.options.tableBodyHeight;
1923
    }
1924

1925
    const tableProps = this.options.setTableProps ? this.options.setTableProps() || {} : {};
1926
    const tableClassNames = clsx(classes.tableRoot, tableProps.className);
1927
    delete tableProps.className; // remove className from props to avoid the className being applied twice
1928

1929
    const dndProps = {};
1930
    if (typeof window !== 'undefined') {
1931
      dndProps.context = window;
1932
    }
1933

1934
    return (
1935
      <Paper elevation={this.options.elevation} ref={this.tableContent} className={paperClasses}>
1936
        {(this.options.selectToolbarPlacement === STP.ALWAYS || selectedRows.data.length > 0 && this.options.selectToolbarPlacement !== STP.NONE) && (
1937
          <TableToolbarSelectComponent
1938
            options={this.options}
1939
            selectedRows={selectedRows}
1940
            onRowsDelete={this.selectRowDelete}
1941
            displayData={displayData}
1942
            selectRowUpdate={this.selectRowUpdate}
1943
            components={this.props.components}
1944
          />
1945
        )}
1946
        {(selectedRows.data.length === 0 ||
1947
          [STP.ABOVE, STP.NONE].indexOf(this.options.selectToolbarPlacement) !== -1) &&
1948
          showToolbar && (
1949
            <TableToolbarComponent
1950
              columns={columns}
1951
              columnOrder={columnOrder}
1952
              displayData={displayData}
1953
              data={data}
1954
              filterData={filterData}
1955
              filterList={filterList}
1956
              filterUpdate={this.filterUpdate}
1957
              updateFilterByType={this.updateFilterByType}
1958
              options={this.options}
1959
              resetFilters={this.resetFilters}
1960
              searchText={searchText}
1961
              searchTextUpdate={this.searchTextUpdate}
1962
              searchClose={this.searchClose}
1963
              tableRef={this.getTableContentRef}
1964
              title={title}
1965
              toggleViewColumn={this.toggleViewColumn}
1966
              updateColumns={this.updateColumns}
1967
              setTableAction={this.setTableAction}
1968
              components={this.props.components}
1969
            />
1970
          )}
1971
        <TableFilterListComponent
1972
          options={this.options}
1973
          serverSideFilterList={this.props.options.serverSideFilterList}
1974
          filterListRenderers={columns.map(c => {
1975
            if (c.customFilterListOptions && c.customFilterListOptions.render) return c.customFilterListOptions.render;
1976
            // DEPRECATED: This option is being replaced with customFilterListOptions.render
1977
            if (c.customFilterListRender) return c.customFilterListRender;
1978

1979
            return f => f;
1980
          })}
1981
          customFilterListUpdate={columns.map(c => {
1982
            return c.customFilterListOptions && c.customFilterListOptions.update
1983
              ? c.customFilterListOptions.update
1984
              : null;
1985
          })}
1986
          filterList={filterList}
1987
          filterUpdate={this.filterUpdate}
1988
          columnNames={columnNames}
1989
        />
1990
        <div style={{ position: 'relative', ...tableHeightVal }} className={responsiveClass}>
1991
          {(this.options.resizableColumns === true ||
1992
            (this.options.resizableColumns && this.options.resizableColumns.enabled)) && (
1993
            <TableResizeComponent
1994
              key={rowCount}
1995
              columnOrder={columnOrder}
1996
              updateDividers={fn => (this.updateDividers = fn)}
1997
              setResizeable={fn => (this.setHeadResizeable = fn)}
1998
              options={this.props.options}
1999
              tableId={this.options.tableId}
2000
            />
2001
          )}
2002
          {(() => {
2003
            const components = (
2004
              <MuiTable
2005
                ref={el => (this.tableRef = el)}
2006
                tabIndex={'0'}
2007
                role={'grid'}
2008
                className={tableClassNames}
2009
                {...tableProps}>
2010
                <caption className={classes.caption}>{title}</caption>
2011
                <TableHeadComponent
2012
                  columns={columns}
2013
                  activeColumn={activeColumn}
2014
                  data={displayData}
2015
                  count={rowCount}
2016
                  page={page}
2017
                  rowsPerPage={rowsPerPage}
2018
                  selectedRows={selectedRows}
2019
                  selectRowUpdate={this.selectRowUpdate}
2020
                  toggleSort={this.toggleSortColumn}
2021
                  setCellRef={this.setHeadCellRef}
2022
                  expandedRows={expandedRows}
2023
                  areAllRowsExpanded={this.areAllRowsExpanded}
2024
                  toggleAllExpandableRows={this.toggleAllExpandableRows}
2025
                  options={this.options}
2026
                  sortOrder={sortOrder}
2027
                  columnOrder={columnOrder}
2028
                  updateColumnOrder={this.updateColumnOrder}
2029
                  draggableHeadCellRefs={this.draggableHeadCellRefs}
2030
                  tableRef={this.getTableContentRef}
2031
                  tableId={this.options.tableId}
2032
                  timers={this.timers}
2033
                  components={this.props.components}
2034
                />
2035
                <TableBodyComponent
2036
                  data={displayData}
2037
                  count={rowCount}
2038
                  columns={columns}
2039
                  page={page}
2040
                  rowsPerPage={rowsPerPage}
2041
                  selectedRows={selectedRows}
2042
                  selectRowUpdate={this.selectRowUpdate}
2043
                  previousSelectedRow={previousSelectedRow}
2044
                  expandedRows={expandedRows}
2045
                  toggleExpandRow={this.toggleExpandRow}
2046
                  options={this.options}
2047
                  columnOrder={columnOrder}
2048
                  filterList={filterList}
2049
                  components={this.props.components}
2050
                  tableId={this.options.tableId}
2051
                />
2052
                {this.options.customTableBodyFooterRender
2053
                  ? this.options.customTableBodyFooterRender({
2054
                      data: displayData,
2055
                      count: rowCount,
2056
                      columns,
2057
                      selectedRows,
2058
                      selectableRows: this.options.selectableRows,
2059
                    })
2060
                  : null}
2061
              </MuiTable>
2062
            );
2063
            if (DragDropBackend) {
2064
              return (
2065
                <DndProvider backend={DragDropBackend} {...dndProps}>
2066
                  {components}
2067
                </DndProvider>
2068
              );
2069
            }
2070

2071
            return components;
2072
          })()}
2073
        </div>
2074
        <TableFooterComponent
2075
          options={this.options}
2076
          page={page}
2077
          rowCount={rowCount}
2078
          rowsPerPage={rowsPerPage}
2079
          changeRowsPerPage={this.changeRowsPerPage}
2080
          changePage={this.changePage}
2081
        />
2082
        <div className={classes.liveAnnounce} aria-live={'polite'}>
2083
          {announceText}
2084
        </div>
2085
      </Paper>
2086
    );
2087
  }
2088
}
2089

2090
export default withStyles(MUIDataTable, defaultTableStyles, { name: 'MUIDataTable' });
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc