aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/framework/src/components/material-table
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/framework/src/components/material-table')
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts54
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/index.tsx552
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx88
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx101
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx149
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/utilities.ts263
6 files changed, 1207 insertions, 0 deletions
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts b/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts
new file mode 100644
index 000000000..c05142084
--- /dev/null
+++ b/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts
@@ -0,0 +1,54 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+
+export enum ColumnType {
+ text,
+ numeric,
+ boolean,
+ custom
+}
+
+type CustomControl<TData> = {
+ className?: string;
+ style?: React.CSSProperties;
+ rowData: TData;
+}
+
+export type ColumnModel<TData> = {
+ title?: string;
+ disablePadding?: boolean;
+ width?: string | number;
+ className?: string;
+ style?: React.CSSProperties;
+ align?: 'inherit' | 'left' | 'center' | 'right' | 'justify';
+ disableSorting?: boolean;
+ disableFilter?: boolean;
+} & ({
+ property: string;
+ type: ColumnType.custom;
+ customControl: React.ComponentType<CustomControl<TData>>;
+} | {
+ property: keyof TData;
+ type: ColumnType.boolean;
+ labels?: { "true": string, "false": string };
+} | {
+ property: keyof TData;
+ type?: ColumnType.numeric | ColumnType.text;
+}); \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
new file mode 100644
index 000000000..3e31c5e03
--- /dev/null
+++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
@@ -0,0 +1,552 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+
+import Table from '@material-ui/core/Table';
+import TableBody from '@material-ui/core/TableBody';
+import TableCell from '@material-ui/core/TableCell';
+import TablePagination from '@material-ui/core/TablePagination';
+import TableRow from '@material-ui/core/TableRow';
+import Paper from '@material-ui/core/Paper';
+import Checkbox from '@material-ui/core/Checkbox';
+
+import { TableToolbar } from './tableToolbar';
+import { EnhancedTableHead } from './tableHead';
+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 dataType = { [prop: string]: propType };
+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>>;
+
+function desc(a: dataType, b: dataType, orderBy: string) {
+ if ((b[orderBy] || "") < (a[orderBy] || "")) {
+ return -1;
+ }
+ if ((b[orderBy] || "") > (a[orderBy] || "")) {
+ return 1;
+ }
+ return 0;
+}
+
+function stableSort(array: dataType[], cmp: (a: dataType, b: dataType) => number) {
+ const stabilizedThis = array.map((el, index) => [el, index]) as [dataType, number][];
+ stabilizedThis.sort((a, b) => {
+ const order = cmp(a[0], b[0]);
+ if (order !== 0) return order;
+ return a[1] - b[1];
+ });
+ return stabilizedThis.map(el => el[0]);
+}
+
+function getSorting(order: 'asc' | 'desc' | null, orderBy: string) {
+ return order === 'desc' ? (a: dataType, b: dataType) => desc(a, b, orderBy) : (a: dataType, b: dataType) => -desc(a, b, orderBy);
+}
+
+const styles = (theme: Theme) => createStyles({
+ root: {
+ width: '100%',
+ marginTop: theme.spacing(3),
+ },
+ table: {
+ minWidth: 1020,
+ },
+ tableWrapper: {
+ overflowX: 'auto',
+ },
+});
+
+export type MaterialTableComponentState<TData = {}> = {
+ order: 'asc' | 'desc';
+ orderBy: string | null;
+ selected: any[] | null;
+ rows: TData[];
+ total: number;
+ page: number;
+ rowsPerPage: number;
+ loading: boolean;
+ showFilter: boolean;
+ filter: { [property: string]: string };
+};
+
+export type TableApi = { forceRefresh?: () => Promise<void> };
+
+type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & {
+ columns: ColumnModel<TData>[];
+ 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 }[];
+ 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 & {
+ onToggleFilter: () => void;
+ onFilterChanged: (property: string, filterTerm: string) => void;
+ onHandleChangePage: (page: number) => void;
+ onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void;
+ onHandleRequestSort: (property: string) => void;
+};
+
+type MaterialTableComponentProps<TData = {}> =
+ MaterialTableComponentPropsWithRows<TData> |
+ MaterialTableComponentPropsWithRequestData<TData> |
+ MaterialTableComponentPropsWithExternalState<TData>;
+
+function isMaterialTableComponentPropsWithRows(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRows {
+ return (props as MaterialTableComponentPropsWithRows).rows !== undefined && (props as MaterialTableComponentPropsWithRows).rows instanceof Array;
+}
+
+function isMaterialTableComponentPropsWithRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRequestData {
+ return (props as MaterialTableComponentPropsWithRequestData).onRequestData !== undefined && (props as MaterialTableComponentPropsWithRequestData).onRequestData instanceof Function;
+}
+
+function isMaterialTableComponentPropsWithRowsAndRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithExternalState {
+ const propsWithExternalState = (props as MaterialTableComponentPropsWithExternalState)
+ return propsWithExternalState.onFilterChanged instanceof Function ||
+ propsWithExternalState.onHandleChangePage instanceof Function ||
+ propsWithExternalState.onHandleChangeRowsPerPage instanceof Function ||
+ propsWithExternalState.onToggleFilter instanceof Function ||
+ propsWithExternalState.onHandleRequestSort instanceof Function
+}
+
+class MaterialTableComponent<TData extends {} = {}> extends React.Component<MaterialTableComponentProps, MaterialTableComponentState> {
+
+ constructor(props: MaterialTableComponentProps) {
+ super(props);
+
+ const page = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.page : 0;
+ const rowsPerPage = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.rowsPerPage || 10 : 10;
+
+ this.state = {
+ filter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.filter || {} : {},
+ showFilter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.showFilter : false,
+ loading: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.loading : false,
+ order: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.order : 'asc',
+ 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) || [],
+ total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0,
+ page,
+ rowsPerPage,
+ };
+
+ if (isMaterialTableComponentPropsWithRequestData(this.props)) {
+ this.update();
+
+ if (this.props.tableApi) {
+ this.props.tableApi.forceRefresh = () => this.update();
+ }
+ }
+ }
+ render(): JSX.Element {
+ const { classes, columns } = this.props;
+ 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 toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) }
+ return (
+ <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}
+ />
+ <TableBody>
+ {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)}
+ role="checkbox"
+ aria-checked={isSelected}
+ tabIndex={-1}
+ key={entryId}
+ selected={isSelected}
+ >
+ {this.props.enableSelection
+ ? <TableCell padding="checkbox" style={{ width: "50px" }}>
+ <Checkbox checked={isSelected} />
+ </TableCell>
+ : null
+ }
+ {
+ this.props.columns.map(
+ col => {
+ 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} />
+ : 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}>{String(entry[col.property])}</span>
+ }
+ </TableCell>
+ );
+ }
+ )
+ }
+ </TableRow>
+ );
+ })}
+ {emptyRows > 0 && (
+ <TableRow style={{ height: 49 * emptyRows }}>
+ <TableCell colSpan={this.props.columns.length} />
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </div>
+ <TablePagination
+ rowsPerPageOptions={[5, 10, 20, 50]}
+ component="div"
+ count={rowCount}
+ rowsPerPage={rowsPerPage}
+ page={page}
+ backIconButtonProps={{
+ 'aria-label': 'Previous Page',
+ }}
+ nextIconButtonProps={{
+ 'aria-label': 'Next Page',
+ }}
+ onChangePage={this.onHandleChangePage}
+ onChangeRowsPerPage={this.onHandleChangeRowsPerPage}
+ />
+ </Paper>
+ );
+ }
+
+ static getDerivedStateFromProps(props: MaterialTableComponentProps, state: MaterialTableComponentState & { _rawRows: {}[] }): MaterialTableComponentState & { _rawRows: {}[] } {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(props)) {
+ return {
+ ...state,
+ rows: props.rows,
+ total: props.total,
+ orderBy: props.orderBy,
+ order: props.order,
+ filter: props.filter,
+ loading: props.loading,
+ showFilter: props.showFilter,
+ page: props.page,
+ rowsPerPage: props.rowsPerPage
+ }
+ } else if (isMaterialTableComponentPropsWithRows(props) && props.asynchronus && state._rawRows !== props.rows) {
+ const newState = MaterialTableComponent.updateRows(props, state);
+ return {
+ ...state,
+ ...newState,
+ _rawRows: props.rows || []
+ };
+ }
+ return state;
+ }
+
+ private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } {
+
+ const { page, rowsPerPage, order, orderBy, filter } = state;
+
+ try {
+ let data: dataType[] = props.rows || [];
+ let filtered = false;
+ if (state.showFilter) {
+ Object.keys(filter).forEach(prop => {
+ const exp = filter[prop];
+ filtered = filtered || exp !== undefined;
+ data = exp !== undefined ? data.filter((val) => {
+ const value = val[prop];
+
+ 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;
+
+ 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
+ };
+ }
+
+
+ } catch (e) {
+ console.error(e);
+ return {
+ rows: [],
+ total: 0,
+ page: page
+ }
+ }
+ }
+
+ private async update() {
+ if (isMaterialTableComponentPropsWithRequestData(this.props)) {
+ const response = await Promise.resolve(
+ this.props.onRequestData(
+ this.state.page, this.state.rowsPerPage, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {})
+ );
+ this.setState(response);
+ } else {
+ let updateResult = MaterialTableComponent.updateRows(this.props, this.state);
+ this.setState(updateResult);
+ }
+ }
+
+ private onFilterChanged = (property: string, filterTerm: string) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onFilterChanged(property, filterTerm);
+ return;
+ }
+ if (this.props.disableFilter) return;
+ const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property);
+ if (colDefinition && colDefinition.disableFilter) return;
+
+ const filter = { ...this.state.filter, [property]: filterTerm };
+ this.setState({
+ filter
+ }, this.update);
+ };
+
+ private onHandleRequestSort = (event: React.SyntheticEvent, property: string) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onHandleRequestSort(property);
+ return;
+ }
+ if (this.props.disableSorting) return;
+ const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property);
+ if (colDefinition && colDefinition.disableSorting) return;
+
+ const orderBy = this.state.orderBy === property && this.state.order === 'desc' ? null : property;
+ const order = this.state.orderBy === property && this.state.order === 'asc' ? 'desc' : 'asc';
+ this.setState({
+ order,
+ orderBy
+ }, this.update);
+ };
+
+ handleSelectAllClick: () => {};
+
+ private onHandleChangePage = (event: any | null, page: number) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onHandleChangePage(page);
+ return;
+ }
+ this.setState({
+ page
+ }, this.update);
+ };
+
+ private onHandleChangeRowsPerPage = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onHandleChangeRowsPerPage(+(event && event.target.value));
+ return;
+ }
+ const rowsPerPage = +(event && event.target.value);
+ if (rowsPerPage && rowsPerPage > 0) {
+ this.setState({
+ rowsPerPage
+ }, this.update);
+ }
+ };
+
+ private isSelected(id: string | number): boolean {
+ let selected = this.state.selected || [];
+ const selectedIndex = selected.indexOf(id);
+ return (selectedIndex > -1);
+ }
+
+ 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) {
+ return;
+ }
+ let selected = this.state.selected || [];
+ const selectedIndex = selected.indexOf(id);
+ if (selectedIndex > -1) {
+ selected = [
+ ...selected.slice(0, selectedIndex),
+ ...selected.slice(selectedIndex + 1)
+ ];
+ } else {
+ selected = [
+ ...selected,
+ id
+ ];
+ }
+ this.setState({
+ selected
+ });
+ }
+
+
+ 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 || {})
+ );
+ data = result.rows;
+ this.setState({ loading: true });
+ } 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;
+ }
+
+ if (data && data.length > 0) {
+ csv.push(this.props.columns.map(col => col.title || col.property).join(',') + "\r\n");
+ 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.
+ try {
+ // Specify the filename using the File constructor, but ...
+ file = new File(csv, "export.csv", properties);
+ } catch (e) {
+ // ... fall back to the Blob constructor if that isn't supported.
+ file = new Blob(csv, properties);
+ }
+ }
+ if (!file) return;
+ var reader = new FileReader();
+ reader.onload = function (e) {
+ const dataUri = reader.result as any;
+ const link = document.createElement("a");
+ if (typeof link.download === 'string') {
+ link.href = dataUri;
+ link.download = "export.csv";
+
+ //Firefox requires the link to be in the body
+ document.body.appendChild(link);
+
+ //simulate click
+ link.click();
+
+ //remove the link when done
+ document.body.removeChild(link);
+ } else {
+ window.open(dataUri);
+ }
+ }
+ reader.readAsDataURL(file);
+
+ // const url = URL.createObjectURL(file);
+ // window.location.replace(url);
+ }
+}
+
+export type MaterialTableCtorType<TData extends {} = {}> = new () => React.Component<Omit<MaterialTableComponentProps<TData>, 'classes'>>;
+
+export const MaterialTable = withStyles(styles)(MaterialTableComponent);
+export default MaterialTable; \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
new file mode 100644
index 000000000..737ea85f9
--- /dev/null
+++ b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
@@ -0,0 +1,88 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+import { ColumnModel, ColumnType } from './columnModel';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+
+
+import TableCell from '@material-ui/core/TableCell';
+import TableRow from '@material-ui/core/TableRow';
+import Input from '@material-ui/core/Input';
+import { Select, FormControl, InputLabel, MenuItem } from '@material-ui/core';
+
+
+const styles = (theme: Theme) => createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ input: {
+ margin: theme.spacing(1),
+ },
+});
+
+interface IEnhancedTableFilterComponentProps extends WithStyles<typeof styles> {
+ onFilterChanged: (property: string, filterTerm: string) => void;
+ filter: { [property: string]: string };
+ columns: ColumnModel<{}>[];
+ enableSelection?: boolean;
+}
+
+class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterComponentProps> {
+ createFilterHandler = (property: string) => (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
+ this.props.onFilterChanged && this.props.onFilterChanged(property, event.target.value);
+ };
+
+ render() {
+ const { columns, filter, classes } = this.props;
+ return (
+ <TableRow>
+ {this.props.enableSelection
+ ? <TableCell padding="checkbox" style={{ width: "50px" }}>
+ </TableCell>
+ : null
+ }
+ {columns.map(col => {
+ const style = col.width ? { width: col.width } : {};
+ return (
+ <TableCell
+ key={col.property}
+ padding={col.disablePadding ? 'none' : 'default'}
+ style={style}
+ >
+ {col.disableFilter || (col.type === ColumnType.custom)
+ ? null
+ : (col.type === ColumnType.boolean)
+ ? <Select className={classes.input} value={filter[col.property] !== undefined ? filter[col.property] : ''} onChange={this.createFilterHandler(col.property)} inputProps={{ name: `${col.property}-bool`, id: `${col.property}-bool` }} >
+ <MenuItem value={undefined}>
+ <em>None</em>
+ </MenuItem>
+ <MenuItem value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem>
+ <MenuItem value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem>
+ </Select>
+ : <Input className={classes.input} inputProps={{ 'aria-label': 'Filter' }} value={filter[col.property] || ''} onChange={this.createFilterHandler(col.property)} />}
+ </TableCell>
+ );
+ }, this)}
+ </TableRow>
+ );
+ }
+}
+
+export const EnhancedTableFilter = withStyles(styles)(EnhancedTableFilterComponent); \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx
new file mode 100644
index 000000000..428f4cf3f
--- /dev/null
+++ b/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx
@@ -0,0 +1,101 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+import { ColumnModel, ColumnType } from './columnModel';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+
+import TableSortLabel from '@material-ui/core/TableSortLabel';
+import TableCell from '@material-ui/core/TableCell';
+import TableHead from '@material-ui/core/TableHead';
+import TableRow from '@material-ui/core/TableRow';
+import Checkbox from '@material-ui/core/Checkbox';
+import Tooltip from '@material-ui/core/Tooltip';
+
+interface IEnhancedTableHeadComponentProps {
+ numSelected: number | null;
+ onRequestSort: (event: React.SyntheticEvent, property: string) => void;
+ onSelectAllClick: () => void;
+ order: 'asc' | 'desc';
+ orderBy: string | null;
+ rowCount: number;
+ columns: ColumnModel<{}>[];
+ enableSelection?: boolean;
+}
+
+class EnhancedTableHeadComponent extends React.Component<IEnhancedTableHeadComponentProps> {
+ createSortHandler = (property: string) => (event: React.SyntheticEvent) => {
+ this.props.onRequestSort(event, property);
+ };
+
+ render() {
+ const { onSelectAllClick, order, orderBy, numSelected, rowCount, columns } = this.props;
+
+ return (
+ <TableHead>
+ <TableRow>
+ { this.props.enableSelection
+ ? <TableCell padding="checkbox" style={ { width: "50px" } }>
+ <Checkbox
+ indeterminate={ numSelected && numSelected > 0 && numSelected < rowCount || undefined }
+ checked={ numSelected === rowCount }
+ onChange={ onSelectAllClick }
+ />
+ </TableCell>
+ : null
+ }
+ { columns.map(col => {
+ const style = col.width ? { width: col.width } : {};
+ return (
+ <TableCell
+ key={ col.property }
+ align={ col.type === ColumnType.numeric ? 'right' : 'left' }
+ padding={ col.disablePadding ? 'none' : 'default' }
+ sortDirection={ orderBy === (col.property) ? order : false }
+ style={ style }
+ >
+ { col.disableSorting || (col.type === ColumnType.custom)
+ ? <TableSortLabel
+ active={ false }
+ direction={ undefined }
+ >
+ { col.title || col.property }
+ </TableSortLabel>
+ : <Tooltip
+ title="Sort"
+ placement={ col.type === ColumnType.numeric ? 'bottom-end' : 'bottom-start' }
+ enterDelay={ 300 }
+ >
+ <TableSortLabel
+ active={ orderBy === col.property }
+ direction={ order || undefined }
+ onClick={ this.createSortHandler(col.property) }
+ >
+ { col.title || col.property }
+ </TableSortLabel>
+ </Tooltip> }
+ </TableCell>
+ );
+ }, this) }
+ </TableRow>
+ </TableHead>
+ );
+ }
+}
+
+export const EnhancedTableHead = EnhancedTableHeadComponent; \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx
new file mode 100644
index 000000000..a4080b51b
--- /dev/null
+++ b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx
@@ -0,0 +1,149 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
+
+import IconButton from '@material-ui/core/IconButton';
+import Tooltip from '@material-ui/core/Tooltip';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+import DeleteIcon from '@material-ui/icons/Delete';
+import MoreIcon from '@material-ui/icons/MoreVert';
+import FilterListIcon from '@material-ui/icons/FilterList';
+import MenuItem from '@material-ui/core/MenuItem';
+import Menu from '@material-ui/core/Menu';
+import { lighten } from '@material-ui/core/styles/colorManipulator';
+import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon';
+
+const styles = (theme: Theme) => createStyles({
+ root: {
+ paddingRight: theme.spacing(1),
+ },
+ highlight:
+ theme.palette.type === 'light'
+ ? {
+ color: theme.palette.secondary.main,
+ backgroundColor: lighten(theme.palette.secondary.light, 0.85),
+ }
+ : {
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.secondary.dark,
+ },
+ spacer: {
+ flex: '1 1 100%',
+ },
+ actions: {
+ color: theme.palette.text.secondary,
+ display: "flex",
+ flex: "auto",
+ flexDirection: "row"
+ },
+ title: {
+ flex: '0 0 auto',
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+});
+
+interface ITableToolbarComponentProps extends WithStyles<typeof styles> {
+ numSelected: number | null;
+ title?: string;
+ tableId?: string;
+ customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[];
+ onToggleFilter: () => void;
+ onExportToCsv: () => void;
+}
+
+class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, { anchorEl: EventTarget & HTMLElement | null }> {
+ constructor(props: ITableToolbarComponentProps) {
+ super(props);
+
+ this.state = {
+ anchorEl: null
+ };
+ }
+
+ private handleMenu = (event: React.MouseEvent<HTMLElement>) => {
+ this.setState({ anchorEl: event.currentTarget });
+ };
+
+ private handleClose = () => {
+ this.setState({ anchorEl: null });
+ };
+ render() {
+ const { numSelected, classes } = this.props;
+ const open = !!this.state.anchorEl;
+ const buttonPrefix = this.props.tableId !== undefined ? this.props.tableId + '-' : '';
+ return (
+ <Toolbar className={`${classes.root} ${numSelected && numSelected > 0 ? classes.highlight : ''} `} >
+ <div className={classes.title}>
+ {numSelected && numSelected > 0 ? (
+ <Typography color="inherit" variant="subtitle1">
+ {numSelected} selected
+ </Typography>
+ ) : (
+ <Typography variant="h5" id="tableTitle">
+ {this.props.title || null}
+ </Typography>
+ )}
+ </div>
+ <div className={classes.spacer} />
+ <div className={classes.actions}>
+ {this.props.customActionButtons
+ ? this.props.customActionButtons.map((action, ind) => (
+ <Tooltip key={`custom-action-${ind}`} title={action.tooltip}>
+ <IconButton aria-label={buttonPrefix + `custom-action-${ind}`} onClick={() => action.onClick()}>
+ <action.icon />
+ </IconButton>
+ </Tooltip>
+ ))
+ : null}
+ {numSelected && numSelected > 0 ? (
+ <Tooltip title="Delete">
+ <IconButton aria-label={buttonPrefix + "delete"}>
+ <DeleteIcon />
+ </IconButton>
+ </Tooltip>
+ ) : (
+ <Tooltip title="Filter list">
+ <IconButton aria-label={buttonPrefix + "filter-list"} onClick={() => { this.props.onToggleFilter && this.props.onToggleFilter() }}>
+ <FilterListIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ <Tooltip title="Actions">
+ <IconButton color="inherit"
+ aria-owns={open ? 'menu-appbar' : undefined}
+ aria-haspopup="true"
+ onClick={this.handleMenu} >
+ <MoreIcon />
+ </IconButton>
+ </Tooltip>
+ <Menu id="menu-appbar" anchorEl={this.state.anchorEl} anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} onClose={this.handleClose} >
+ <MenuItem onClick={this.props.onExportToCsv}>Export as CSV</MenuItem>
+ </Menu>
+ </div>
+ </Toolbar>
+ );
+ }
+};
+
+export const TableToolbar = withStyles(styles)(TableToolbarComponent); \ No newline at end of file
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts
new file mode 100644
index 000000000..6e8902c07
--- /dev/null
+++ b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts
@@ -0,0 +1,263 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import { Action, IActionHandler } from '../../flux/action';
+import { Dispatch } from '../../flux/store';
+
+import { AddErrorInfoAction } from '../../actions/errorActions';
+import { IApplicationStoreState } from '../../store/applicationStore';
+
+import { DataCallback } from ".";
+export interface IExternalTableState<TData> {
+ order: 'asc' | 'desc';
+ orderBy: string | null;
+ selected: any[] | null;
+ rows: TData[];
+ total: number;
+ page: number;
+ rowsPerPage: number;
+ loading: boolean;
+ showFilter: boolean;
+ filter: { [property: string]: string };
+ preFilter: { [property: string]: string };
+}
+
+/** Create an actionHandler and actions for external table states. */
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>) {
+
+ //#region Actions
+ abstract class TableAction extends Action { }
+
+
+ class RequestSortAction extends TableAction {
+ constructor(public orderBy: string) {
+ super();
+ }
+ }
+
+ class SetSelectedAction extends TableAction {
+ constructor(public selected: TData[] | null) {
+ super();
+ }
+ }
+
+ class SetPageAction extends TableAction {
+ constructor(public page: number) {
+ super();
+ }
+ }
+
+ class SetRowsPerPageAction extends TableAction {
+ constructor(public rowsPerPage: number) {
+ super();
+ }
+ }
+
+ class SetPreFilterChangedAction extends TableAction {
+ constructor(public preFilter: { [key: string]: string }) {
+ super();
+ }
+ }
+
+ class SetFilterChangedAction extends TableAction {
+ constructor(public filter: { [key: string]: string }) {
+ super();
+ }
+ }
+
+ class SetShowFilterAction extends TableAction {
+ constructor(public show: boolean) {
+ super();
+ }
+ }
+
+ class RefreshAction extends TableAction {
+ constructor() {
+ super();
+ }
+ }
+
+ class SetResultAction extends TableAction {
+ constructor(public result: { page: number, total: number, rows: TData[] }) {
+ super();
+ }
+ }
+
+ // #endregion
+
+ //#region Action Handler
+ const externalTableStateInit: IExternalTableState<TData> = {
+ order: 'asc',
+ orderBy: null,
+ selected: null,
+ rows: [],
+ total: 0,
+ page: 0,
+ rowsPerPage: 10,
+ loading: false,
+ showFilter: false,
+ filter: {},
+ preFilter: {}
+ };
+
+ const externalTableStateActionHandler: IActionHandler<IExternalTableState<TData>> = (state = externalTableStateInit, action) => {
+ if (!(action instanceof TableAction)) return state;
+ if (action instanceof RefreshAction) {
+ state = {
+ ...state,
+ loading: true
+ }
+ } else if (action instanceof SetResultAction) {
+ state = {
+ ...state,
+ loading: false,
+ rows: action.result.rows,
+ total: action.result.total,
+ page: action.result.page,
+ }
+ } else if (action instanceof RequestSortAction) {
+ state = {
+ ...state,
+ loading: true,
+ orderBy: state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy,
+ order: state.orderBy === action.orderBy && state.order === 'asc' ? 'desc' : 'asc',
+ }
+ } else if (action instanceof SetShowFilterAction) {
+ state = {
+ ...state,
+ loading: true,
+ showFilter: action.show
+ }
+ } else if (action instanceof SetPreFilterChangedAction) {
+ state = {
+ ...state,
+ loading: true,
+ preFilter: action.preFilter
+ }
+ } else if (action instanceof SetFilterChangedAction) {
+ state = {
+ ...state,
+ loading: true,
+ filter: action.filter
+ }
+ } else if (action instanceof SetPageAction) {
+ state = {
+ ...state,
+ loading: true,
+ page: action.page
+ }
+ } else if (action instanceof SetRowsPerPageAction) {
+ state = {
+ ...state,
+ loading: true,
+ rowsPerPage: action.rowsPerPage
+ }
+ }
+ return state;
+ }
+
+ //const createTableAction(tableAction)
+
+ //#endregion
+ const reloadAction = (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
+ dispatch(new RefreshAction());
+ const ownState = selectState(getAppState());
+ const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) };
+ Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => {
+
+ if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate
+
+ let newPage = Math.floor(result.total / ownState.rowsPerPage);
+
+ Promise.resolve(callback(newPage, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result1 => {
+ dispatch(new SetResultAction(result1));
+ });
+
+
+ } else {
+ dispatch(new SetResultAction(result));
+ }
+
+
+ }).catch(error => new AddErrorInfoAction(error));
+ };
+
+ const createPreActions = (dispatch: Dispatch, skipRefresh: boolean = false) => {
+ return {
+ onPreFilterChanged: (preFilter: { [key: string]: string }) => {
+ dispatch(new SetPreFilterChangedAction(preFilter));
+ (!skipRefresh) && dispatch(reloadAction);
+ }
+ };
+ }
+
+ const createActions = (dispatch: Dispatch, skipRefresh: boolean = false) => {
+ return {
+ onRefresh: () => {
+ dispatch(reloadAction);
+ },
+ onHandleRequestSort: (orderBy: string) => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new RequestSortAction(orderBy));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onToggleFilter: () => {
+ dispatch((dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
+ const { showFilter } = selectState(getAppState());
+ dispatch(new SetShowFilterAction(!showFilter));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onFilterChanged: (property: string, filterTerm: string) => {
+ dispatch((dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
+ let { filter } = selectState(getAppState());
+ filter = { ...filter, [property]: filterTerm };
+ dispatch(new SetFilterChangedAction(filter));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onHandleChangePage: (page: number) => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new SetPageAction(page));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onHandleChangeRowsPerPage: (rowsPerPage: number | null) => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new SetRowsPerPageAction(rowsPerPage || 10));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ }
+ // selected:
+ };
+ };
+
+ const createProperties = (state: IApplicationStoreState) => {
+ return {
+ ...selectState(state)
+ }
+ }
+
+ return {
+ reloadAction: reloadAction,
+ createActions: createActions,
+ createProperties: createProperties,
+ createPreActions: createPreActions,
+ actionHandler: externalTableStateActionHandler
+ }
+} \ No newline at end of file