diff options
Diffstat (limited to 'sdnr/wt/odlux/framework/src/components/material-table/index.tsx')
-rw-r--r-- | sdnr/wt/odlux/framework/src/components/material-table/index.tsx | 231 |
1 files changed, 151 insertions, 80 deletions
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx index d89ced23a..3e31c5e03 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx @@ -33,19 +33,21 @@ import { EnhancedTableFilter } from './tableFilter'; import { ColumnModel, ColumnType } from './columnModel'; import { Omit } from '@material-ui/core'; import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon'; +import { replaceHyphen } from '../../utilities/yangHelper'; +import { string } from 'prop-types'; export { ColumnModel, ColumnType } from './columnModel'; -type propType = string | number | null | undefined | (string|number)[]; +type propType = string | number | null | undefined | (string | number)[]; type dataType = { [prop: string]: propType }; -type resultType<TData = dataType> = { page: number, rowCount: number, rows: TData[] }; +type resultType<TData = dataType> = { page: number, total: number, rows: TData[] }; -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>>; +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>>; function desc(a: dataType, b: dataType, orderBy: string) { - if ((b[orderBy] || "") < (a[orderBy] || "") ) { + if ((b[orderBy] || "") < (a[orderBy] || "")) { return -1; } - if ((b[orderBy] || "") > (a[orderBy] || "") ) { + if ((b[orderBy] || "") > (a[orderBy] || "")) { return 1; } return 0; @@ -68,7 +70,7 @@ function getSorting(order: 'asc' | 'desc' | null, orderBy: string) { const styles = (theme: Theme) => createStyles({ root: { width: '100%', - marginTop: theme.spacing.unit * 3, + marginTop: theme.spacing(3), }, table: { minWidth: 1020, @@ -83,7 +85,7 @@ export type MaterialTableComponentState<TData = {}> = { orderBy: string | null; selected: any[] | null; rows: TData[]; - rowCount: number; + total: number; page: number; rowsPerPage: number; loading: boolean; @@ -95,18 +97,19 @@ export type TableApi = { forceRefresh?: () => Promise<void> }; type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & { columns: ColumnModel<TData>[]; - idProperty: keyof TData | ((data: TData) => React.Key ); + idProperty: keyof TData | ((data: TData) => React.Key); + tableId?: string; title?: string; enableSelection?: boolean; disableSorting?: boolean; disableFilter?: boolean; - customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; + customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void; }; -type MaterialTableComponentPropsWithRows<TData={}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; }; -type MaterialTableComponentPropsWithRequestData<TData={}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; }; -type MaterialTableComponentPropsWithExternalState<TData={}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & { +type MaterialTableComponentPropsWithRows<TData = {}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; }; +type MaterialTableComponentPropsWithRequestData<TData = {}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; }; +type MaterialTableComponentPropsWithExternalState<TData = {}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & { onToggleFilter: () => void; onFilterChanged: (property: string, filterTerm: string) => void; onHandleChangePage: (page: number) => void; @@ -152,7 +155,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate orderBy: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.orderBy : null, selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null, rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [], - rowCount: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, + total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, page, rowsPerPage, }; @@ -167,58 +170,58 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } render(): JSX.Element { const { classes, columns } = this.props; - const { rows, rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; + const { rows, total: rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowCount - page * rowsPerPage); - 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; + 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; const toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) } return ( - <Paper className={ classes.root }> - <TableToolbar numSelected={ selected && selected.length } title={ this.props.title } customActionButtons={ this.props.customActionButtons } onExportToCsv={ this.exportToCsv } - onToggleFilter={ toggleFilter } /> - <div className={ classes.tableWrapper }> - <Table className={ classes.table } aria-labelledby="tableTitle"> + <Paper className={classes.root}> + <TableToolbar tableId={this.props.tableId} numSelected={selected && selected.length} title={this.props.title} customActionButtons={this.props.customActionButtons} onExportToCsv={this.exportToCsv} + onToggleFilter={toggleFilter} /> + <div className={classes.tableWrapper}> + <Table className={classes.table} aria-labelledby="tableTitle"> <EnhancedTableHead - columns={ columns } - numSelected={ selected && selected.length } - order={ order } - orderBy={ orderBy } - onSelectAllClick={ this.handleSelectAllClick } - onRequestSort={ this.onHandleRequestSort } - rowCount={ rows.length } - enableSelection={ this.props.enableSelection } + columns={columns} + numSelected={selected && selected.length} + order={order} + orderBy={orderBy} + onSelectAllClick={this.handleSelectAllClick} + onRequestSort={this.onHandleRequestSort} + rowCount={rows.length} + enableSelection={this.props.enableSelection} /> <TableBody> - { showFilter && <EnhancedTableFilter columns={ columns } filter={ filter } onFilterChanged={ this.onFilterChanged } enableSelection={this.props.enableSelection} /> || null } - { rows // may need ordering here + {showFilter && <EnhancedTableFilter columns={columns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null} + {rows // may need ordering here .map((entry: TData & { [key: string]: any }) => { const entryId = getId(entry); const isSelected = this.isSelected(entryId); return ( <TableRow hover - onClick={ event => this.handleClick(event, entry, entryId) } + onClick={event => this.handleClick(event, entry, entryId)} role="checkbox" - aria-checked={ isSelected } - tabIndex={ -1 } - key={ entryId } - selected={ isSelected } + aria-checked={isSelected} + tabIndex={-1} + key={entryId} + selected={isSelected} > - { this.props.enableSelection - ? <TableCell padding="checkbox" style={ { width: "50px" } }> - <Checkbox checked={ isSelected } /> + {this.props.enableSelection + ? <TableCell padding="checkbox" style={{ width: "50px" }}> + <Checkbox checked={isSelected} /> </TableCell> - : null + : null } { this.props.columns.map( col => { - const style = col.width ? { width: col.width } : { }; + const style = col.width ? { width: col.width } : {}; return ( - <TableCell key={ col.property } align={ col.type === ColumnType.numeric && !col.align ? "right": col.align } style={ style }> - { col.type === ColumnType.custom && col.customControl - ? <col.customControl className={col.className} style={col.style} rowData={ entry } /> + <TableCell key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} style={style}> + {col.type === ColumnType.custom && col.customControl + ? <col.customControl className={col.className} style={col.style} rowData={entry} /> : col.type === ColumnType.boolean - ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true": "false"] : String(entry[col.property]) }</span> + ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true" : "false"] : String(entry[col.property])}</span> : <span className={col.className} style={col.style}>{String(entry[col.property])}</span> } </TableCell> @@ -228,29 +231,29 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } </TableRow> ); - }) } - { emptyRows > 0 && ( - <TableRow style={ { height: 49 * emptyRows } }> - <TableCell colSpan={ this.props.columns.length } /> + })} + {emptyRows > 0 && ( + <TableRow style={{ height: 49 * emptyRows }}> + <TableCell colSpan={this.props.columns.length} /> </TableRow> - ) } + )} </TableBody> </Table> </div> <TablePagination - rowsPerPageOptions={[5, 10, 20, 50] } + rowsPerPageOptions={[5, 10, 20, 50]} component="div" - count={ rowCount } - rowsPerPage={ rowsPerPage } - page={ page } - backIconButtonProps={ { + count={rowCount} + rowsPerPage={rowsPerPage} + page={page} + backIconButtonProps={{ 'aria-label': 'Previous Page', - } } - nextIconButtonProps={ { + }} + nextIconButtonProps={{ 'aria-label': 'Next Page', - } } - onChangePage={ this.onHandleChangePage } - onChangeRowsPerPage={ this.onHandleChangeRowsPerPage } + }} + onChangePage={this.onHandleChangePage} + onChangeRowsPerPage={this.onHandleChangeRowsPerPage} /> </Paper> ); @@ -261,7 +264,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return { ...state, rows: props.rows, - rowCount: props.rowCount, + total: props.total, orderBy: props.orderBy, order: props.order, filter: props.filter, @@ -281,9 +284,11 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return state; } - private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], rowCount: number } { + private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } { + + const { page, rowsPerPage, order, orderBy, filter } = state; + try { - const { page, rowsPerPage, order, orderBy, filter } = state; let data: dataType[] = props.rows || []; let filtered = false; if (state.showFilter) { @@ -292,25 +297,84 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate filtered = filtered || exp !== undefined; data = exp !== undefined ? data.filter((val) => { const value = val[prop]; - return (value == exp) || (value && value.toString().indexOf(String(exp)) > -1); + + if (value) { + + if (typeof exp === 'boolean') { + return value == exp; + + } else if (typeof exp === 'string') { + + const valueAsString = value.toString(); + if (exp.length === 0) return value; + + const regex = new RegExp("\\*", "g"); + const regex2 = new RegExp("\\?", "g"); + + const countStar = (exp.match(regex) || []).length; + const countQuestionmarks = (exp.match(regex2) || []).length; + + if (countStar > 0 || countQuestionmarks > 0) { + let editableExpression = exp; + + if (!exp.startsWith('*')) { + editableExpression = '^' + exp; + } + + if (!exp.endsWith('*')) { + editableExpression = editableExpression + '$'; + } + + const expressionAsRegex = editableExpression.replace(/\*/g, ".*").replace(/\?/g, "."); + + return valueAsString.match(new RegExp(expressionAsRegex, "g")); + } + else if (exp.includes('>=')) { + return Number(valueAsString) >= Number(exp.replace('>=', '')); + } else if (exp.includes('<=')) { + return Number(valueAsString) <= Number(exp.replace('<=', '')); + } else + if (exp.includes('>')) { + return Number(valueAsString) > Number(exp.replace('>', '')); + } else if (exp.includes('<')) { + return Number(valueAsString) < Number(exp.replace('<', '')); + } + } + } + + return (value == exp) }) : data; }); } const rowCount = data.length; - data = (orderBy && order - ? stableSort(data, getSorting(order, orderBy)) - : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + if (page > 0 && rowsPerPage * page > rowCount) { //if result is smaller than the currently shown page, new search and repaginate + let newPage = Math.floor(rowCount / rowsPerPage); + return { + rows: data, + total: rowCount, + page: newPage + }; + } else { + data = (orderBy && order + ? stableSort(data, getSorting(order, orderBy)) + : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + + return { + rows: data, + total: rowCount, + page: page + }; + } - return { - rows: data, - rowCount - }; - } catch{ + + } catch (e) { + console.error(e); return { rows: [], - rowCount: 0 + total: 0, + page: page } } } @@ -323,7 +387,8 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate ); this.setState(response); } else { - this.setState(MaterialTableComponent.updateRows(this.props, this.state)); + let updateResult = MaterialTableComponent.updateRows(this.props, this.state); + this.setState(updateResult); } } @@ -361,7 +426,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate handleSelectAllClick: () => {}; - private onHandleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, page: number) => { + private onHandleChangePage = (event: any | null, page: number) => { if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { this.props.onHandleChangePage(page); return; @@ -390,12 +455,12 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return (selectedIndex > -1); } - private handleClick(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData, id: string | number): void { + private handleClick(event: any, rowData: TData, id: string | number): void { if (this.props.onHandleClick instanceof Function) { this.props.onHandleClick(event, rowData); return; } - if (!this.props.enableSelection){ + if (!this.props.enableSelection) { return; } let selected = this.state.selected || []; @@ -416,20 +481,26 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate }); } + private exportToCsv = async () => { let file; let data: dataType[] | null = null; let csv: string[] = []; - if (isMaterialTableComponentPropsWithRequestData(this.props)) { + // table with extra request handler this.setState({ loading: true }); const result = await Promise.resolve( - this.props.onRequestData( 0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) + this.props.onRequestData(0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) ); data = result.rows; this.setState({ loading: true }); - } else { + } else if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + // table with generated handlers note: exports data shown on current page + data = this.props.rows; + } + else { + // table with local data data = MaterialTableComponent.updateRows(this.props, this.state).rows; } @@ -438,7 +509,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate this.state.rows && this.state.rows.forEach((row: any) => { csv.push(this.props.columns.map(col => row[col.property]).join(',') + "\r\n"); }); - const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. + const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. try { // Specify the filename using the File constructor, but ... file = new File(csv, "export.csv", properties); |