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

gregnb / mui-datatables / #1889

22 Jul 2026 11:47AM UTC coverage: 84.398% (+6.1%) from 78.298%
#1889

push

971 of 1258 branches covered (77.19%)

Branch coverage included in aggregate %.

1301 of 1434 relevant lines covered (90.73%)

103.4 hits per line

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

85.53
/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';
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';
1✔
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: {
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',
1✔
89
  },
90
});
91

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

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

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

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

106
// Select Toolbar Placement options
107
const STP = {
108
  REPLACE: 'replace',
109
  ABOVE: 'above',
110
  NONE: 'none',
1✔
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,
133
            searchable: PropTypes.bool,
134
            download: PropTypes.bool,
135
            viewColumns: PropTypes.bool,
136
            filterList: PropTypes.array,
137
            filterOptions: PropTypes.oneOfType([
138
              PropTypes.array,
139
              PropTypes.shape({
140
                names: PropTypes.array,
141
                logic: PropTypes.func,
142
                display: PropTypes.func,
143
              }),
144
            ]),
145
            filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect', 'textField', 'custom']),
146
            customHeadRender: PropTypes.func,
147
            customBodyRender: PropTypes.func,
148
            customBodyRenderLite: PropTypes.func,
149
            customHeadLabelRender: PropTypes.func,
150
            customFilterListOptions: PropTypes.oneOfType([
151
              PropTypes.shape({
152
                render: PropTypes.func,
153
                update: PropTypes.func,
154
              }),
155
            ]),
156
            customFilterListRender: PropTypes.func,
157
            setCellProps: PropTypes.func,
158
            setCellHeaderProps: PropTypes.func,
159
            sortThirdClickReset: PropTypes.bool,
160
            sortDescFirst: PropTypes.bool,
161
          }),
162
        }),
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,
189
        separator: PropTypes.string,
190
        filterOptions: PropTypes.shape({
191
          useDisplayedColumnsOnly: PropTypes.bool,
192
          useDisplayedRowsOnly: PropTypes.bool,
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,
199
      fixedSelectColumn: PropTypes.bool,
200
      getTextLabels: PropTypes.func,
201
      isRowExpandable: PropTypes.func,
202
      isRowSelectable: PropTypes.func,
203
      jumpToPage: PropTypes.bool,
204
      onDownload: PropTypes.func,
205
      onFilterChange: PropTypes.func,
206
      onFilterChipClose: PropTypes.func,
207
      onFilterConfirm: PropTypes.func,
208
      onFilterDialogOpen: PropTypes.func,
209
      onFilterDialogClose: PropTypes.func,
210
      onRowClick: PropTypes.func,
211
      onRowsExpand: PropTypes.func,
212
      onRowExpansionChange: PropTypes.func,
213
      onRowsSelect: PropTypes.func,
214
      onRowSelectionChange: PropTypes.func,
215
      onTableChange: PropTypes.func,
216
      onTableInit: PropTypes.func,
217
      page: PropTypes.number,
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,
226
      tableId: PropTypes.string,
227
      tableBodyHeight: PropTypes.string,
228
      tableBodyMaxHeight: PropTypes.string,
229
      renderExpandableRow: PropTypes.func,
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,
235
      rowsPerPageOptions: PropTypes.array,
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,
244
      selectToolbarPlacement: PropTypes.oneOfType([
245
        PropTypes.bool,
246
        PropTypes.oneOf([STP.REPLACE, STP.ABOVE, STP.NONE, STP.ALWAYS]),
247
      ]),
248
      setTableProps: PropTypes.func,
1✔
249
      sort: PropTypes.bool,
250
      sortOrder: PropTypes.object,
251
      storageKey: PropTypes.string,
252
      viewColumns: PropTypes.oneOf([true, false, 'true', 'false', 'disabled']),
253
    }),
254
    /** Pass and use className to style MUIDataTable as desired */
255
    className: PropTypes.string,
256
    components: PropTypes.objectOf(PropTypes.any),
257
  };
258

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

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

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

98✔
314
    this.mergeDefaultOptions(props);
315

98✔
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);
84✔
320

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

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

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

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

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

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

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

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.
102✔
353
      this.setState({ page: 0 });
354
    }
1✔
355

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

365
  updateOptions(options, props) {
366
    // set backwards compatibility options
98✔
367
    if (props.options.disableToolbarSelect === true && props.options.selectToolbarPlacement === undefined) {
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
373
    if (!props.options.tableId) {
374
      props.options.tableId = (Math.random() + '').replace(/\./, '');
375
    }
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
379
      if (key === 'textLabels' || key === 'downloadOptions') return merge(objValue, srcValue);
380
      return;
381
    });
382

383
    this.handleOptionDeprecation(props);
384
  }
385

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

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

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

442
  handleOptionDeprecation = props => {
443
    if (typeof this.options.selectableRows === 'boolean') {
444
      this.warnDep(
1✔
445
        'Using a boolean for selectableRows has been deprecated. Please use string option: multiple | single | none',
446
      );
447
      this.options.selectableRows = this.options.selectableRows ? 'multiple' : 'none';
448
    }
449
    if (['standard', 'vertical', 'verticalAlways', 'simple'].indexOf(this.options.responsive) === -1) {
450
      if (
102✔
451
        [
1✔
452
          'scrollMaxHeight',
453
          'scrollFullHeight',
454
          'stacked',
455
          'stackedFullWidth',
102✔
456
          'scrollFullHeightFullWidth',
1✔
457
          'scroll',
458
        ].indexOf(this.options.responsive) !== -1
459
      ) {
460
        this.warnDep(
102✔
461
          this.options.responsive +
1!
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 {
465
        this.warnInfo(
×
466
          this.options.responsive +
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',
1!
468
        );
1!
469
      }
470
    }
471
    if (this.options.onRowsSelect) {
×
472
      this.warnDep(
473
        'onRowsSelect has been renamed onRowSelectionChange. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
1✔
474
      );
475
    }
476
    if (this.options.onRowsExpand) {
477
      this.warnDep(
102✔
478
        'onRowsExpand has been renamed onRowExpansionChange. More info: https://github.com/gregnb/mui-datatables/tree/master/docs/v2_to_v3_guide.md',
1✔
479
      );
480
    }
481
    if (this.options.fixedHeaderOptions) {
482
      if (
483
        typeof this.options.fixedHeaderOptions.yAxis !== 'undefined' &&
102✔
484
        typeof this.options.fixedHeader === 'undefined'
493✔
485
      ) {
1✔
486
        this.options.fixedHeader = this.options.fixedHeaderOptions.yAxis;
487
      }
488
      if (
489
        typeof this.options.fixedHeaderOptions.xAxis !== 'undefined' &&
490
        typeof this.options.fixedSelectColumn === 'undefined'
491
      ) {
102✔
492
        this.options.fixedSelectColumn = this.options.fixedHeaderOptions.xAxis;
2✔
493
      }
494
      this.warnDep(
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',
496
      );
497
    }
102✔
498
    if (this.options.serverSideFilterList) {
1✔
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
      );
502
    }
503

504
    props.columns.map(c => {
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
        );
98✔
509
      }
98✔
510
    });
98!
511

512
    if (this.options.disableToolbarSelect === true) {
98✔
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
    }
98!
517

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

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

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

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

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

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

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

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

579
  setHeadCellRef = (index, pos, el) => {
580
    this.draggableHeadCellRefs[index] = el;
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
503✔
586
  getTableContentRef = () => this.tableContent.current;
503✔
587

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

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

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

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

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

503✔
627
          if (options.sortDirection === null || options.sortDirection) {
628
            this.warnDep(
503✔
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',
503✔
630
            );
631
          }
632
        }
104!
633

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

104✔
644
        columnOptions = {
645
          name: column.name,
646
          label: column.label ? column.label : column.name,
98✔
647
          ...columnOptions,
648
          ...options,
103✔
649
        };
498✔
650
      } else {
3✔
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 };
103✔
657
      }
103✔
658

72✔
659
      columnData.push(columnOptions);
80!
660

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

665
    if (Array.isArray(newColumnOrder)) {
393✔
666
      columnOrder = newColumnOrder;
667
    } else if (
393✔
668
      Array.isArray(prevColumnOrder) &&
1,886✔
669
      Array.isArray(newColumns) &&
1,886✔
670
      Array.isArray(prevColumns) &&
671
      newColumns.length === prevColumns.length
672
    ) {
72✔
673
      columnOrder = prevColumnOrder;
674
    }
103✔
675

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

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

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

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

103!
698
    return transformedData;
103✔
699
  };
500✔
700

1✔
701
  setTableData(props, status, dataUpdated, callback = () => {}, fromConstructor = false) {
1✔
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,
104✔
708
    );
104✔
709

710
    let sortIndex = null;
104✔
711
    let sortDirection = 'none';
102✔
712
    let tableMeta;
713

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

412✔
720
      // if no sortOrder, check and see if there's a sortDirection on one of the columns (deprecation notice for this is given above)
412✔
721
      if (!sortOrder.direction) {
412✔
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;
1,973✔
726
          }
1,954✔
727
        });
1,091✔
728
      }
1,091✔
729
    }
1,091✔
730

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

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

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

743
    let page = this.state.page;
744
    if (typeof this.options.page === 'number') {
1,954✔
745
      page = this.options.page;
1,340✔
746
    }
614✔
747

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

752
        if (typeof tableData[rowIndex] === 'undefined') {
12✔
753
          tableData.push({
754
            index: status === TABLE_LOAD.INITIAL ? rowIndex : data[rowIndex].index,
755
            data: status === TABLE_LOAD.INITIAL ? data[rowIndex] : data[rowIndex].data,
12✔
756
          });
6✔
757
        }
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);
503✔
764

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

772
          if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
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
503✔
775
            value = value.toString ? value.toString() : '';
3✔
776
          }
500!
777

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

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

797
      if (column.filterOptions) {
798
        if (Array.isArray(column.filterOptions)) {
799
          filterData[colIndex] = cloneDeep(column.filterOptions);
800
          this.warnDep(
104✔
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
        }
104!
806
      }
807

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

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

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

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

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

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

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

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

2✔
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];
2✔
865

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

104✔
873
        selectedRowsData.data.push({ index: rowPos, dataIndex: this.options.rowsSelected[0] });
2✔
874
        selectedRowsData.lookup[this.options.rowsSelected[0]] = true;
2✔
875
      } else if (
876
        this.options.rowsSelected &&
877
        this.options.rowsSelected.length > 1 &&
878
        this.options.selectableRows === 'single'
104✔
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 => {
891
          let rowPos = row;
892

104✔
893
          for (let cIndex = 0; cIndex < this.state.displayData.length; cIndex++) {
98✔
894
            if (this.state.displayData[cIndex].dataIndex === row) {
895
              rowPos = cIndex;
6✔
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 */
556✔
914
    let stateUpdates = {
556✔
915
      columns: columns,
556✔
916
      filterData: filterData,
917
      filterList: filterList,
556✔
918
      searchText: searchText,
2,669✔
919
      selectedRows: selectedRowsData,
2,669✔
920
      expandedRows: expandedRowsData,
2,669✔
921
      count: this.options.count,
922
      data: tableData,
2,669✔
923
      sortOrder: sortOrder,
15✔
924
      rowsPerPage,
2,654✔
925
      page,
1,506✔
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
   */
1,506✔
940
  computeDisplayRow(
941
    columns,
942
    row,
943
    rowIndex,
944
    filterList,
1,506✔
945
    searchText,
946
    dataForTableMeta,
947
    options,
1,506✔
948
    props,
3,622✔
949
    currentTableData,
950
  ) {
1,464!
951
    let isFiltered = false;
952
    let isSearchFound = false;
953
    let displayRow = [];
954

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

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

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

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

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

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

999
      const filterVal = filterList[index];
556!
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' &&
556✔
1009
          Array.isArray(columnValue) === false &&
8!
1010
          filterVal.indexOf(columnValue) < 0
×
1011
        ) {
1012
          isFiltered = true;
1013
        } else if (filterType !== 'textField' && Array.isArray(columnValue)) {
8✔
1014
          if (options.filterArrayFullMatch) {
1015
            //true if every filterVal exists in columnVal, false otherwise
1016
            const isFullMatch = filterVal.every(el => {
548✔
1017
              return columnValue.indexOf(el) >= 0;
473✔
1018
            });
1019
            //if it is not a fullMatch, filter row out
1020
            if (!isFullMatch) {
98✔
1021
              isFiltered = true;
108✔
1022
            }
108✔
1023
          } else {
1024
            const isAnyMatch = filterVal.some(el => {
108!
1025
              return columnValue.indexOf(el) >= 0;
108✔
1026
            });
108✔
1027
            //if no value matches, filter row out
1028
            if (!isAnyMatch) {
1029
              isFiltered = true;
108✔
1030
            }
1031
          }
1032
        }
98✔
1033
      }
1✔
1034

1✔
1035
      if (
1✔
1036
        searchText &&
1037
        column.display !== 'excluded' &&
1✔
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;
1✔
1047

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

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

1062
      return displayRow;
1063
    }
1✔
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;
98✔
1079
  };
2,598✔
1080

1081
  updateDataCol = (row, index, value) => {
2,598✔
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,
140✔
1094
      );
140✔
1095
      const funcResult = prevState.columns[index].customBodyRender(value, tableMeta);
1096

140✔
1097
      const filterValue =
556✔
1098
        React.isValidElement(funcResult) && funcResult.props.value
556✔
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
      }
556✔
1111

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

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

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

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

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

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

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

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

6✔
1189
  updateColumns = newColumns => {
1190
    this.setState(
1191
      prevState => {
1192
        return {
1193
          columns: newColumns,
1194
        };
6!
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
      },
6✔
1204
    );
1205
  };
6✔
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() || {};
6✔
1223

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

6✔
1226
    return tableProps;
6✔
1227
  }
2✔
1228

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

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

1244
        if (columns[index].name === this.state.sortOrder.name) {
2!
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
        }
98✔
1252

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

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

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

2✔
1267
        if (this.options.serverSide) {
2✔
1268
          newState = {
1269
            ...newState,
2!
1270
            data: prevState.data,
1271
            displayData: prevState.displayData,
1272
            selectedRows: prevState.selectedRows,
1273
            sortOrder: newSortOrder,
1274
          };
2✔
1275
        } else {
2!
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,
98✔
1283
              sortedData.data,
3✔
1284
              prevState.filterList,
3✔
1285
              prevState.searchText,
9!
1286
              null,
1287
              this.props,
3!
1288
            ),
1289
            selectedRows: sortedData.selectedRows,
1290
            sortOrder: newSortOrder,
1291
            previousSelectedRow: null,
1292
          };
3✔
1293
        }
3!
1294

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

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

1307
  changeRowsPerPage = rows => {
3!
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
        }
3✔
1321
      },
3✔
1322
    );
2✔
1323
  };
1324

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

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

1356
  searchTextUpdate = text => {
98✔
1357
    this.setState(
21✔
1358
      prevState => ({
1359
        searchText: text && text.length ? text : null,
21✔
1360
        page: 0,
21✔
1361
        displayData: this.options.serverSide
1362
          ? prevState.displayData
21✔
1363
          : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text, null, this.props),
1364
      }),
1365
      () => {
21✔
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 {
21✔
1380
          filterList: filterList,
21✔
1381
          displayData: this.options.serverSide
5✔
1382
            ? prevState.displayData
1383
            : this.getDisplayData(
21!
1384
                prevState.columns,
1385
                prevState.data,
1386
                filterList,
1387
                prevState.searchText,
1388
                null,
1389
                this.props,
98✔
1390
              ),
2✔
1391
        };
2✔
1392
      },
2✔
1393
      () => {
1394
        this.setTableAction('resetFilters');
2✔
1395
        if (this.options.onFilterChange) {
1396
          this.options.onFilterChange(null, this.state.filterList, 'reset', null);
1✔
1397
        }
4✔
1398
      },
4!
1399
    );
4✔
1400
  };
1401

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

1✔
1405
    switch (type) {
4✔
1406
      case 'checkbox':
4!
1407
        filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(value);
4!
1408
        break;
4✔
1409
      case 'chip':
1410
        filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(value);
1411
        break;
1412
      case 'multiselect':
4✔
1413
        filterList[index] = value === '' ? [] : value;
4✔
1414
        break;
1415
      case 'dropdown':
1416
        filterList[index] = value;
1417
        break;
1418
      case 'custom':
1419
        if (customUpdate) {
2✔
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
    }
2✔
1428
  };
2!
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
98✔
1440
            ? prevState.displayData
1✔
1441
            : this.getDisplayData(
1442
                prevState.columns,
1443
                prevState.data,
98✔
1444
                filterList,
1✔
1445
                prevState.searchText,
1446
                null,
1✔
1447
                this.props,
1448
              ),
1449
          previousSelectedRow: null,
1450
        };
1451
      },
1✔
1452
      () => {
1!
1453
        this.setTableAction('filterChange');
1✔
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
    );
98✔
1460
  };
2✔
1461

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

1468
    if (expandedRowsData.length > 0) {
1469
      // collapse all
3✔
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))) {
1✔
1473
          affecttedRows.push(expandedRowsData.splice(ii, 1));
1474
        }
1475
      }
1✔
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);
1✔
1487
            affecttedRows.push(newItem);
1488
          }
1489
        }
1490
      }
1491
    }
98✔
1492

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

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

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

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

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

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

1549
    this.setTableData(
3✔
1550
      {
1551
        columns: this.props.columns,
3✔
1552
        data: cleanRows,
12!
1553
        options: {
12✔
1554
          filterList: filterList,
12✔
1555
        },
1556
      },
1557
      TABLE_LOAD.UPDATE,
3✔
1558
      true,
3✔
1559
      () => {
1560
        this.setTableAction('rowDelete');
1561
      },
3!
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

3!
1574
    for (var cIndex = 0; cIndex < expandedRowsData.length; cIndex++) {
×
1575
      if (expandedRowsData[cIndex].dataIndex === dataIndex) {
×
1576
        shouldCollapseExpandedRow = true;
1577
        break;
1578
      }
3✔
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);
3✔
1589
    }
3!
1590

×
1591
    this.setState(
1592
      {
1593
        curExpandedRows: hasRemovedRow ? removedRow : [row],
×
1594
        expandedRows: {
1595
          lookup: buildMap(expandedRowsData),
3!
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
        }
18✔
1605
      },
16✔
1606
    );
1607
  };
16✔
1608

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

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

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

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

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

1647
          if (isDeselect) {
16✔
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,
16✔
1657
            },
16✔
1658
            previousSelectedRow: null,
2✔
1659
          };
1660
        },
1661
        () => {
1✔
1662
          this.setTableAction('rowSelectionChange');
1663
          if (this.options.onRowSelectionChange) {
14!
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,
2!
1673
              this.state.selectedRows.data.map(item => item.dataIndex),
2✔
1674
            );
1675
          }
4✔
1676
        },
2✔
1677
      );
1678
    } else if (type === 'cell') {
2✔
1679
      this.setState(
1680
        prevState => {
1681
          const { dataIndex } = value;
1682
          let selectedRows = [...prevState.selectedRows.data];
1683
          let rowPos = -1;
1684

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

2!
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') {
8!
1705
            selectedRows = [value];
1706
          } else {
32✔
1707
            // multiple
1708
            selectedRows.push(value);
1709

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

8✔
1721
          return {
32✔
1722
            selectedRows: {
32✔
1723
              lookup: buildMap(selectedRows),
32!
1724
              data: selectedRows,
×
1725
            },
1726
            previousSelectedRow: value,
1727
          };
1728
        },
8✔
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
          }
182✔
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(
182✔
1761
              this.state.selectedRows.data,
1762
              this.state.selectedRows.data,
182✔
1763
              this.state.selectedRows.data.map(item => item.dataIndex),
182!
1764
            );
182✔
1765
          } else if (this.options.onRowsSelect) {
182✔
1766
            this.options.onRowsSelect(
182✔
1767
              this.state.selectedRows.data,
182✔
1768
              this.state.selectedRows.data,
182✔
1769
              this.state.selectedRows.data.map(item => item.dataIndex),
1770
            );
182✔
1771
          }
182✔
1772
        },
182✔
1773
      );
887✔
1774
    }
1775
  };
1,250✔
1776

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

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

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

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

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

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

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

182!
1825
  render() {
182✔
1826
    const {
1827
      classes,
1828
      className,
182!
1829
      title,
182✔
1830
      components: {
182✔
1831
        TableBody,
1832
        TableFilterList,
182✔
1833
        TableFooter,
182!
1834
        TableHead,
182✔
1835
        TableResize,
1836
        TableToolbar,
1837
        TableToolbarSelect,
182✔
1838
        DragDropBackend = HTML5Backend,
1839
      },
228✔
1840
    } = this.props;
1841
    const {
1842
      announceText,
1843
      activeColumn,
1844
      data,
1845
      displayData,
1846
      columns,
1847
      page,
1848
      filterData,
1849
      filterList,
530✔
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;
887✔
1878

1879
    switch (responsiveOption) {
716!
1880
      // deprecated
1881
      case 'scroll':
716✔
1882
        responsiveClass = classes.responsiveScroll;
1883
        maxHeight = '499px';
1884
        break;
887!
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';
364!
1894
        break;
1895
      // deprecated
1896
      case 'scrollFullHeightFullWidth':
1897
        responsiveClass = classes.responsiveScrollFullHeight;
1898
        paperClasses = `${classes.paperResponsiveScrollFullHeightFullWidth} ${className}`;
1✔
1899
        break;
1✔
1900
      // deprecated
1901
      case 'stacked':
1902
        responsiveClass = classes.responsiveStacked;
1903
        maxHeight = 'none';
1904
        break;
1905
      // deprecated
1906
      case 'stackedFullWidth':
68✔
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}
182!
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 TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc