2 * ============LICENSE_START========================================================================
3 * ONAP : ccsdk feature sdnr wt odlux
4 * =================================================================================================
5 * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
6 * =================================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software distributed under the License
13 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14 * or implied. See the License for the specific language governing permissions and limitations under
16 * ============LICENSE_END==========================================================================
18 import * as React from 'react';
19 import { Theme } from '@mui/material/styles';
21 import { WithStyles } from '@mui/styles';
22 import withStyles from '@mui/styles/withStyles';
23 import createStyles from '@mui/styles/createStyles';
25 import Table from '@mui/material/Table';
26 import TableBody from '@mui/material/TableBody';
27 import TableCell from '@mui/material/TableCell';
28 import TableContainer from '@mui/material/TableContainer';
29 import TablePagination from '@mui/material/TablePagination';
30 import TableRow from '@mui/material/TableRow';
31 import Paper from '@mui/material/Paper';
32 import Checkbox from '@mui/material/Checkbox';
34 import { TableToolbar } from './tableToolbar';
35 import { EnhancedTableHead } from './tableHead';
36 import { EnhancedTableFilter } from './tableFilter';
38 import { ColumnModel, ColumnType } from './columnModel';
39 import { Menu, Typography } from '@mui/material';
40 import { DistributiveOmit } from '@mui/types';
42 import makeStyles from '@mui/styles/makeStyles';
44 import { SvgIconProps } from '@mui/material/SvgIcon';
46 import { DividerTypeMap } from '@mui/material/Divider';
47 import { MenuItemProps } from '@mui/material/MenuItem';
48 import { flexbox } from '@mui/system';
49 import { RowDisabled } from './utilities';
50 import { toAriaLabel } from '../../utilities/yangHelper';
51 export { ColumnModel, ColumnType } from './columnModel';
53 type propType = string | number | null | undefined | (string | number)[];
54 type dataType = { [prop: string]: propType };
55 type resultType<TData = dataType> = { page: number, total: number, rows: TData[] };
57 export type DataCallback<TData = dataType> = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) => resultType<TData> | Promise<resultType<TData>>;
59 function regExpEscape(s: string) {
60 return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
63 function wildcardCheck(input: string, pattern: string) {
64 if (!pattern) return true;
65 const regex = new RegExp(
66 (!pattern.startsWith('*') ? '^' : '') +
67 pattern.split(/\*+/).map(p => p.split(/\?+/).map(regExpEscape).join('.')).join('.*') +
68 (!pattern.endsWith('*') ? '$' : '')
70 return input.match(regex) !== null && input.match(regex)!.length >= 1;
73 function desc(a: dataType, b: dataType, orderBy: string) {
74 if ((b[orderBy] || "") < (a[orderBy] || "")) {
77 if ((b[orderBy] || "") > (a[orderBy] || "")) {
83 function stableSort(array: dataType[], cmp: (a: dataType, b: dataType) => number) {
84 const stabilizedThis = array.map((el, index) => [el, index]) as [dataType, number][];
85 stabilizedThis.sort((a, b) => {
86 const order = cmp(a[0], b[0]);
87 if (order !== 0) return order;
90 return stabilizedThis.map(el => el[0]);
93 function getSorting(order: 'asc' | 'desc' | null, orderBy: string) {
94 return order === 'desc' ? (a: dataType, b: dataType) => desc(a, b, orderBy) : (a: dataType, b: dataType) => -desc(a, b, orderBy);
97 const styles = (theme: Theme) => createStyles({
101 marginTop: theme.spacing(3),
102 position: "relative",
103 boxSizing: "border-box",
105 flexDirection: "column",
116 const useTableRowExtStyles = makeStyles((theme: Theme) => createStyles({
118 color: "rgba(180, 180, 180, 0.7)",
122 type GetStatelessComponentProps<T> = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any;
123 type TableRowExtProps = GetStatelessComponentProps<typeof TableRow> & { disabled: boolean };
124 const TableRowExt : React.FC<TableRowExtProps> = (props) => {
125 const [disabled, setDisabled] = React.useState(true);
126 const classes = useTableRowExtStyles();
128 const onMouseDown = (ev: React.MouseEvent<HTMLElement>) => {
130 setDisabled(!disabled);
132 ev.stopPropagation();
133 } else if (props.disabled && disabled) {
135 ev.stopPropagation();
140 <TableRow {...{...props, color: props.disabled && disabled ? '#a0a0a0' : undefined , className: props.disabled && disabled ? classes.disabled : '', onMouseDown, onContextMenu: props.disabled && disabled ? onMouseDown : props.onContextMenu } } />
144 export type MaterialTableComponentState<TData = {}> = {
145 order: 'asc' | 'desc';
146 orderBy: string | null;
147 selected: any[] | null;
154 hiddenColumns: string[];
155 filter: { [property: string]: string };
158 export type TableApi = { forceRefresh?: () => Promise<void> };
160 type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & {
162 columns: ColumnModel<TData>[];
163 idProperty: keyof TData | ((data: TData) => React.Key);
165 //Note: used to save settings as well. Must be unique across apps. Null tableIds will not get saved to the settings
166 tableId: string | null;
169 stickyHeader?: boolean;
170 allowHtmlHeader?: boolean;
171 defaultSortOrder?: 'asc' | 'desc';
172 defaultSortColumn?: keyof TData;
173 enableSelection?: boolean;
174 disableSorting?: boolean;
175 disableFilter?: boolean;
176 customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, ariaLabel: string, onClick: () => void, disabled?: boolean }[];
177 onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void;
178 createContextMenu?: (row: TData) => React.ReactElement<MenuItemProps | DividerTypeMap<{}, "hr">, React.ComponentType<MenuItemProps | DividerTypeMap<{}, "hr">>>[];
181 type MaterialTableComponentPropsWithRows<TData = {}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; };
182 type MaterialTableComponentPropsWithRequestData<TData = {}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; };
183 type MaterialTableComponentPropsWithExternalState<TData = {}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & {
184 onToggleFilter: () => void;
185 onFilterChanged: (property: string, filterTerm: string) => void;
186 onHandleChangePage: (page: number) => void;
187 onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void;
188 onHandleRequestSort: (property: string) => void;
189 onHideColumns : (columnNames: string[]) => void
190 onShowColumns: (columnNames: string[]) => void
193 type MaterialTableComponentProps<TData = {}> =
194 MaterialTableComponentPropsWithRows<TData> |
195 MaterialTableComponentPropsWithRequestData<TData> |
196 MaterialTableComponentPropsWithExternalState<TData>;
198 function isMaterialTableComponentPropsWithRows(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRows {
199 return (props as MaterialTableComponentPropsWithRows).rows !== undefined && (props as MaterialTableComponentPropsWithRows).rows instanceof Array;
202 function isMaterialTableComponentPropsWithRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRequestData {
203 return (props as MaterialTableComponentPropsWithRequestData).onRequestData !== undefined && (props as MaterialTableComponentPropsWithRequestData).onRequestData instanceof Function;
206 function isMaterialTableComponentPropsWithRowsAndRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithExternalState {
207 const propsWithExternalState = (props as MaterialTableComponentPropsWithExternalState)
208 return propsWithExternalState.onFilterChanged instanceof Function ||
209 propsWithExternalState.onHandleChangePage instanceof Function ||
210 propsWithExternalState.onHandleChangeRowsPerPage instanceof Function ||
211 propsWithExternalState.onToggleFilter instanceof Function ||
212 propsWithExternalState.onHideColumns instanceof Function ||
213 propsWithExternalState.onHandleRequestSort instanceof Function
216 // get settings in here!
219 class MaterialTableComponent<TData extends {} = {}> extends React.Component<MaterialTableComponentProps, MaterialTableComponentState & { contextMenuInfo: { index: number; mouseX?: number; mouseY?: number }; }> {
221 constructor(props: MaterialTableComponentProps) {
225 const page = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.page : 0;
226 const rowsPerPage = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.rowsPerPage || 10 : 10;
229 contextMenuInfo: { index: -1 },
230 filter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.filter || {} : {},
231 showFilter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.showFilter : false,
232 loading: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.loading : false,
233 order: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.order : this.props.defaultSortOrder || 'asc',
234 orderBy: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.orderBy : this.props.defaultSortColumn || null,
235 selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null,
236 rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [],
237 total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0,
238 hiddenColumns: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) && this.props.hiddenColumns || [],
243 if (isMaterialTableComponentPropsWithRequestData(this.props)) {
246 if (this.props.tableApi) {
247 this.props.tableApi.forceRefresh = () => this.update();
251 render(): JSX.Element {
252 const { classes, columns, allowHtmlHeader } = this.props;
253 const { rows, total: rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state;
254 const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowCount - page * rowsPerPage);
255 const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as { [key: string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty;
256 const toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) }
258 const hideColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onHideColumns : (data: string[]) => { const newArray = [...new Set([...this.state.hiddenColumns, ...data])]; this.setState({hiddenColumns:newArray}); }
259 const showColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onShowColumns : (data: string[]) => { const newArray = this.state.hiddenColumns.filter(el=> !data.includes(el)); this.setState({hiddenColumns:newArray}); }
261 const allColumnsHidden = this.props.columns.length === this.state.hiddenColumns.length;
263 <Paper className={this.props.className ? `${classes.root} ${this.props.className}` : classes.root}>
264 <TableContainer className={classes.container}>
265 <TableToolbar tableId={this.props.tableId} numSelected={selected && selected.length} title={this.props.title} customActionButtons={this.props.customActionButtons} onExportToCsv={this.exportToCsv}
266 onToggleFilter={toggleFilter}
268 onHideColumns={hideColumns}
269 onShowColumns={showColumns} />
270 <Table padding="normal" aria-label={this.props.tableId ? this.props.tableId : 'tableTitle'} stickyHeader={this.props.stickyHeader || false} >
272 allowHtmlHeader={allowHtmlHeader || false}
274 numSelected={selected && selected.length}
277 onSelectAllClick={this.handleSelectAllClick}
278 onRequestSort={this.onHandleRequestSort}
279 rowCount={rows.length}
280 enableSelection={this.props.enableSelection}
281 hiddenColumns={this.state.hiddenColumns}
284 {showFilter && <EnhancedTableFilter columns={columns} hiddenColumns={this.state.hiddenColumns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null}
286 {allColumnsHidden ? <Typography variant="body1" textAlign="center">All columns of this table are hidden.</Typography> :
288 rows // may need ordering here
289 .map((entry: TData & { [RowDisabled]?: boolean, [kex: string]: any }, index) => {
290 const entryId = getId(entry);
291 const contextMenu = (this.props.createContextMenu && this.state.contextMenuInfo.index === index && this.props.createContextMenu(entry)) || null;
292 const isSelected = this.isSelected(entryId) || this.state.contextMenuInfo.index === index;
297 if (this.props.createContextMenu) {
304 this.handleClick(event, entry, entryId);
306 onContextMenu={event => {
307 if (this.props.createContextMenu) {
308 event.preventDefault();
309 event.stopPropagation();
310 this.setState({ contextMenuInfo: { index, mouseX: event.clientX - 2, mouseY: event.clientY - 4 } });
314 aria-checked={isSelected}
315 aria-label="table-row"
318 selected={isSelected}
319 disabled={entry[RowDisabled] || false}
321 {this.props.enableSelection
322 ? <TableCell padding="checkbox" style={{ width: "50px", color: entry[RowDisabled] || false ? "inherit" : undefined } }>
323 <Checkbox color='secondary' checked={isSelected} />
329 this.props.columns.map(
331 const style = col.width ? { width: col.width } : {};
334 <TableCell style={ entry[RowDisabled] || false ? { ...style, color: "inherit" } : style } aria-label={col.title? toAriaLabel(col.title) : toAriaLabel(col.property)} key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} >
335 {col.type === ColumnType.custom && col.customControl
336 ? <col.customControl className={col.className} style={col.style} rowData={entry} />
337 : col.type === ColumnType.boolean
338 ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true" : "false"] : String(entry[col.property])}</span>
339 : <span className={col.className} style={col.style}>{String(entry[col.property])}</span>
345 const showColumn = !this.state.hiddenColumns.includes(col.property);
346 return showColumn && tableCell
350 {<Menu open={!!contextMenu} onClose={() => this.setState({ contextMenuInfo: { index: -1 } })} anchorReference="anchorPosition" keepMounted
351 anchorPosition={this.state.contextMenuInfo.mouseY != null && this.state.contextMenuInfo.mouseX != null ? { top: this.state.contextMenuInfo.mouseY, left: this.state.contextMenuInfo.mouseX } : undefined}>
358 <TableRow style={{ height: 49 * emptyRows }}>
359 <TableCell colSpan={this.props.columns.length} />
365 <TablePagination className={classes.pagination}
366 rowsPerPageOptions={[5, 10, 20, 50]}
369 rowsPerPage={rowsPerPage}
371 aria-label={this.props.isPopup ? "popup-table-pagination-footer" : "table-pagination-footer" }
372 backIconButtonProps={{
373 'aria-label': this.props.isPopup ? 'popup-previous-page' : 'previous-page',
375 nextIconButtonProps={{
376 'aria-label': this.props.isPopup ? 'popup-next-page': 'next-page',
378 onPageChange={this.onHandleChangePage}
379 onRowsPerPageChange={this.onHandleChangeRowsPerPage}
385 static getDerivedStateFromProps(props: MaterialTableComponentProps, state: MaterialTableComponentState & { _rawRows: {}[] }): MaterialTableComponentState & { _rawRows: {}[] } {
387 if (isMaterialTableComponentPropsWithRowsAndRequestData(props)) {
392 orderBy: props.orderBy,
394 filter: props.filter,
395 loading: props.loading,
396 showFilter: props.showFilter,
398 hiddenColumns: props.hiddenColumns,
399 rowsPerPage: props.rowsPerPage
401 } else if (isMaterialTableComponentPropsWithRows(props) && props.asynchronus && state._rawRows !== props.rows) {
402 const newState = MaterialTableComponent.updateRows(props, state);
406 _rawRows: props.rows || []
412 private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } {
414 let data = [...(props.rows as dataType[] || [])];
415 const columns = props.columns;
417 const { page, rowsPerPage, order, orderBy, filter } = state;
420 if (state.showFilter) {
421 Object.keys(filter).forEach(prop => {
422 const column = columns.find(c => c.property === prop);
423 const filterExpression = filter[prop];
425 if (!column) throw new Error("Filter for not existing column found.");
427 if (filterExpression != null) {
428 data = data.filter((val) => {
429 const dataValue = val[prop];
431 if (dataValue != null) {
433 if (column.type === ColumnType.boolean) {
435 const boolDataValue = JSON.parse(String(dataValue).toLowerCase());
436 const boolFilterExpression = JSON.parse(String(filterExpression).toLowerCase());
437 return boolDataValue == boolFilterExpression;
439 } else if (column.type === ColumnType.text) {
441 const valueAsString = String(dataValue);
442 const filterExpressionAsString = String(filterExpression).trim();
443 if (filterExpressionAsString.length === 0) return true;
444 return wildcardCheck(valueAsString, filterExpressionAsString);
446 } else if (column.type === ColumnType.numeric){
448 const valueAsNumber = Number(dataValue);
449 const filterExpressionAsString = String(filterExpression).trim();
450 if (filterExpressionAsString.length === 0 || isNaN(valueAsNumber)) return true;
452 if (filterExpressionAsString.startsWith('>=')) {
453 return valueAsNumber >= Number(filterExpressionAsString.substring(2).trim());
454 } else if (filterExpressionAsString.startsWith('<=')) {
455 return valueAsNumber <= Number(filterExpressionAsString.substring(2).trim());
456 } else if (filterExpressionAsString.startsWith('>')) {
457 return valueAsNumber > Number(filterExpressionAsString.substring(1).trim());
458 } else if (filterExpressionAsString.startsWith('<')) {
459 return valueAsNumber < Number(filterExpressionAsString.substring(1).trim());
461 } else if (column.type === ColumnType.date){
462 const valueAsString = String(dataValue);
464 const convertToDate = (valueAsString: string) => {
465 // time value needs to be padded
466 const hasTimeValue = /T\d{2,2}/.test(valueAsString);
467 const indexCollon = valueAsString.indexOf(':');
468 if (hasTimeValue && (indexCollon === -1 || indexCollon >= valueAsString.length-2)) {
469 valueAsString = indexCollon === -1
470 ? valueAsString + ":00"
471 : indexCollon === valueAsString.length-1
472 ? valueAsString + "00"
473 : valueAsString += "0"
475 return new Date(Date.parse(valueAsString));
479 const valueAsDate = new Date(Date.parse(dataValue));
480 const filterExpressionAsString = String(filterExpression).trim();
482 if (filterExpressionAsString.startsWith('>=')) {
483 return valueAsDate >= convertToDate(filterExpressionAsString.substring(2).trim());
484 } else if (filterExpressionAsString.startsWith('<=')) {
485 return valueAsDate <= convertToDate(filterExpressionAsString.substring(2).trim());
486 } else if (filterExpressionAsString.startsWith('>')) {
487 return valueAsDate > convertToDate(filterExpressionAsString.substring(1).trim());
488 } else if (filterExpressionAsString.startsWith('<')) {
489 return valueAsDate < convertToDate(filterExpressionAsString.substring(1).trim());
493 if (filterExpressionAsString.length === 0) return true;
494 return wildcardCheck(valueAsString, filterExpressionAsString);
499 return (dataValue == filterExpression)
505 const rowCount = data.length;
507 if (page > 0 && rowsPerPage * page > rowCount) { //if result is smaller than the currently shown page, new search and repaginate
508 let newPage = Math.floor(rowCount / rowsPerPage);
515 data = (orderBy && order
516 ? stableSort(data, getSorting(order, orderBy))
517 : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
537 private async update() {
538 if (isMaterialTableComponentPropsWithRequestData(this.props)) {
539 const response = await Promise.resolve(
540 this.props.onRequestData(
541 this.state.page, this.state.rowsPerPage, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {})
543 this.setState(response);
545 let updateResult = MaterialTableComponent.updateRows(this.props, this.state);
546 this.setState(updateResult);
550 private onFilterChanged = (property: string, filterTerm: string) => {
551 if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
552 this.props.onFilterChanged(property, filterTerm);
555 if (this.props.disableFilter) return;
556 const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property);
557 if (colDefinition && colDefinition.disableFilter) return;
559 const filter = { ...this.state.filter, [property]: filterTerm };
565 private onHandleRequestSort = (event: React.SyntheticEvent, property: string) => {
566 if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
567 this.props.onHandleRequestSort(property);
570 if (this.props.disableSorting) return;
571 const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property);
572 if (colDefinition && colDefinition.disableSorting) return;
574 const orderBy = this.state.orderBy === property && this.state.order === 'desc' ? null : property;
575 const order = this.state.orderBy === property && this.state.order === 'asc' ? 'desc' : 'asc';
582 handleSelectAllClick: () => {};
584 private onHandleChangePage = (event: any | null, page: number) => {
585 if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
586 this.props.onHandleChangePage(page);
594 private onHandleChangeRowsPerPage = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
595 if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
596 this.props.onHandleChangeRowsPerPage(+(event && event.target.value));
599 const rowsPerPage = +(event && event.target.value);
600 if (rowsPerPage && rowsPerPage > 0) {
607 private isSelected(id: string | number): boolean {
608 let selected = this.state.selected || [];
609 const selectedIndex = selected.indexOf(id);
610 return (selectedIndex > -1);
613 private handleClick(event: any, rowData: TData, id: string | number): void {
614 if (this.props.onHandleClick instanceof Function) {
615 this.props.onHandleClick(event, rowData);
618 if (!this.props.enableSelection) {
621 let selected = this.state.selected || [];
622 const selectedIndex = selected.indexOf(id);
623 if (selectedIndex > -1) {
625 ...selected.slice(0, selectedIndex),
626 ...selected.slice(selectedIndex + 1)
640 private exportToCsv = async () => {
642 let data: dataType[] | null = null;
643 let csv: string[] = [];
645 if (isMaterialTableComponentPropsWithRequestData(this.props)) {
646 // table with extra request handler
647 this.setState({ loading: true });
648 const result = await Promise.resolve(
649 this.props.onRequestData(0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {})
652 this.setState({ loading: true });
653 } else if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
654 // table with generated handlers note: exports data shown on current page
655 data = this.props.rows;
658 // table with local data
659 data = MaterialTableComponent.updateRows(this.props, this.state).rows;
662 if (data && data.length > 0) {
663 csv.push(this.props.columns.map(col => col.title || col.property).join(',') + "\r\n");
664 this.state.rows && this.state.rows.forEach((row: any) => {
665 csv.push(this.props.columns.map(col => row[col.property]).join(',') + "\r\n");
667 const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type.
669 // Specify the filename using the File constructor, but ...
670 file = new File(csv, "export.csv", properties);
672 // ... fall back to the Blob constructor if that isn't supported.
673 file = new Blob(csv, properties);
677 var reader = new FileReader();
678 reader.onload = function (e) {
679 const dataUri = reader.result as any;
680 const link = document.createElement("a");
681 if (typeof link.download === 'string') {
683 link.download = "export.csv";
685 //Firefox requires the link to be in the body
686 document.body.appendChild(link);
691 //remove the link when done
692 document.body.removeChild(link);
694 window.open(dataUri);
697 reader.readAsDataURL(file);
699 // const url = URL.createObjectURL(file);
700 // window.location.replace(url);
704 export type MaterialTableCtorType<TData extends {} = {}> = new () => React.Component<DistributiveOmit<MaterialTableComponentProps<TData>, 'classes'>>;
706 export const MaterialTable = withStyles(styles)(MaterialTableComponent);
707 export default MaterialTable;