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.ts39
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/index.tsx145
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx4
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx3
4 files changed, 119 insertions, 72 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
index c05142084..8124e450d 100644
--- a/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts
+++ b/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts
@@ -1,20 +1,20 @@
-/**
- * ============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==========================================================================
- */
+/**
+ * ============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';
@@ -22,6 +22,7 @@ export enum ColumnType {
text,
numeric,
boolean,
+ date,
custom
}
@@ -34,7 +35,7 @@ type CustomControl<TData> = {
export type ColumnModel<TData> = {
title?: string;
disablePadding?: boolean;
- width?: string | number;
+ width?: string | number ;
className?: string;
style?: React.CSSProperties;
align?: 'inherit' | 'left' | 'center' | 'right' | 'justify';
@@ -50,5 +51,5 @@ export type ColumnModel<TData> = {
labels?: { "true": string, "false": string };
} | {
property: keyof TData;
- type?: ColumnType.numeric | ColumnType.text;
+ type?: ColumnType.numeric | ColumnType.text | ColumnType.date;
}); \ 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
index c5be81914..7d4633bc6 100644
--- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
+++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx
@@ -47,6 +47,20 @@ 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 regExpEscape(s: string) {
+ return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
+};
+
+function wildcardCheck(input: string, pattern: string) {
+ if (!pattern) return true;
+ const regex = new RegExp(
+ (!pattern.startsWith('*') ? '^' : '') +
+ pattern.split(/\*+/).map(p => p.split(/\?+/).map(regExpEscape).join('.')).join('.*') +
+ (!pattern.endsWith('*') ? '$' : '')
+ );
+ return input.match(regex) !== null && input.match(regex)!.length >= 1;
+};
+
function desc(a: dataType, b: dataType, orderBy: string) {
if ((b[orderBy] || "") < (a[orderBy] || "")) {
return -1;
@@ -118,7 +132,7 @@ type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & {
disableFilter?: boolean;
customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[];
onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void;
- createContextMenu?: (row: TData) => React.ReactElement<MenuItemProps | DividerTypeMap<{}, "hr">, React.ComponentType<MenuItemProps | DividerTypeMap<{}, "hr" >>>[];
+ createContextMenu?: (row: TData) => React.ReactElement<MenuItemProps | DividerTypeMap<{}, "hr">, React.ComponentType<MenuItemProps | DividerTypeMap<{}, "hr">>>[];
};
type MaterialTableComponentPropsWithRows<TData = {}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; };
@@ -162,7 +176,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
const rowsPerPage = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.rowsPerPage || 10 : 10;
this.state = {
- contextMenuInfo: {index : -1 },
+ contextMenuInfo: { index: -1 },
filter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.filter || {} : {},
showFilter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.showFilter : false,
loading: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.loading : false,
@@ -234,7 +248,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
}}
role="checkbox"
aria-checked={isSelected}
- aria-label={`${(this.props.tableId ? this.props.tableId : 'table')}-row`}
+ aria-label="table-row"
tabIndex={-1}
key={entryId}
selected={isSelected}
@@ -250,7 +264,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
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}>
+ <TableCell aria-label={col.title? col.title.toLowerCase().replace(/\s/g, "-") : col.property.toLowerCase().replace(/\s/g, "-")} 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
@@ -283,6 +297,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
count={rowCount}
rowsPerPage={rowsPerPage}
page={page}
+ aria-label="table-pagination-footer"
backIconButtonProps={{
'aria-label': 'previous-page',
}}
@@ -323,64 +338,94 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate
private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } {
+ let data = [...props.rows as dataType[] || []];
+ const columns = props.columns;
+
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;
+ const column = columns.find(c => c.property === prop);
+ const filterExpression = filter[prop];
+
+ if (!column) throw new Error("Filter for not existing column found.");
+
+ if (filterExpression != null) {
+ data = data.filter((val) => {
+ const dataValue = val[prop];
+
+ if (dataValue != null) {
+
+ if (column.type === ColumnType.boolean) {
+
+ const boolDataValue = JSON.parse(String(dataValue).toLowerCase());
+ const boolFilterExpression = JSON.parse(String(filterExpression).toLowerCase());
+ return boolDataValue == boolFilterExpression;
+
+ } else if (column.type === ColumnType.text) {
+
+ const valueAsString = String(dataValue);
+ const filterExpressionAsString = String(filterExpression).trim();
+ if (filterExpressionAsString.length === 0) return true;
+ return wildcardCheck(valueAsString, filterExpressionAsString);
+
+ } else if (column.type === ColumnType.numeric){
+
+ const valueAsNumber = Number(dataValue);
+ const filterExpressionAsString = String(filterExpression).trim();
+ if (filterExpressionAsString.length === 0 || isNaN(valueAsNumber)) return true;
+
+ if (filterExpressionAsString.startsWith('>=')) {
+ return valueAsNumber >= Number(filterExpressionAsString.substr(2).trim());
+ } else if (filterExpressionAsString.startsWith('<=')) {
+ return valueAsNumber <= Number(filterExpressionAsString.substr(2).trim());
+ } else if (filterExpressionAsString.startsWith('>')) {
+ return valueAsNumber > Number(filterExpressionAsString.substr(1).trim());
+ } else if (filterExpressionAsString.startsWith('<')) {
+ return valueAsNumber < Number(filterExpressionAsString.substr(1).trim());
}
-
- if (!exp.endsWith('*')) {
- editableExpression = editableExpression + '$';
+ } else if (column.type === ColumnType.date){
+ const valueAsString = String(dataValue);
+
+ const convertToDate = (valueAsString: string) => {
+ // time value needs to be padded
+ const hasTimeValue = /T\d{2,2}/.test(valueAsString);
+ const indexCollon = valueAsString.indexOf(':');
+ if (hasTimeValue && (indexCollon === -1 || indexCollon >= valueAsString.length-2)) {
+ valueAsString = indexCollon === -1
+ ? valueAsString + ":00"
+ : indexCollon === valueAsString.length-1
+ ? valueAsString + "00"
+ : valueAsString += "0"
+ }
+ return new Date(Date.parse(valueAsString));
+ };
+
+ // @ts-ignore
+ const valueAsDate = new Date(Date.parse(dataValue));
+ const filterExpressionAsString = String(filterExpression).trim();
+
+ if (filterExpressionAsString.startsWith('>=')) {
+ return valueAsDate >= convertToDate(filterExpressionAsString.substr(2).trim());
+ } else if (filterExpressionAsString.startsWith('<=')) {
+ return valueAsDate <= convertToDate(filterExpressionAsString.substr(2).trim());
+ } else if (filterExpressionAsString.startsWith('>')) {
+ return valueAsDate > convertToDate(filterExpressionAsString.substr(1).trim());
+ } else if (filterExpressionAsString.startsWith('<')) {
+ return valueAsDate < convertToDate(filterExpressionAsString.substr(1).trim());
}
- const expressionAsRegex = editableExpression.replace(/\*/g, ".*").replace(/\?/g, ".");
+
+ if (filterExpressionAsString.length === 0) return true;
+ return wildcardCheck(valueAsString, filterExpressionAsString);
- 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;
+ return (dataValue == filterExpression)
+ });
+ };
});
}
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
index 76f778eb4..5aefac445 100644
--- a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
+++ b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx
@@ -73,14 +73,14 @@ class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterC
{col.disableFilter || (col.type === ColumnType.custom)
? null
: (col.type === ColumnType.boolean)
- ? <Select className={classes.input} aria-label={col.title ? (col.title as string).toLowerCase() + ' filter' : `${ind + 1}-filter`} value={filter[col.property] !== undefined ? filter[col.property] : ''} onChange={this.createFilterHandler(col.property)} inputProps={{ name: `${col.property}-bool`, id: `${col.property}-bool` }} >
+ ? <Select className={classes.input} aria-label={col.title ? (col.title as string).toLowerCase().replace(/\s/g, "-") + '-filter' : `${ind + 1}-filter`} 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} aria-label="none-value" >
<em>None</em>
</MenuItem>
<MenuItem aria-label="true-value" value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem>
<MenuItem aria-label="false-value" value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem>
</Select>
- : <Input className={classes.input} inputProps={{ 'aria-label': col.title ? (col.title as string).toLowerCase() + ' filter' : `${ind + 1}-filter` }} value={filter[col.property] || ''} onChange={this.createFilterHandler(col.property)} />}
+ : <Input className={classes.input} inputProps={{ 'aria-label': col.title ? (col.title as string).toLowerCase().replace(/\s/g, "-") + '-filter' : `${ind + 1}-filter` }} value={filter[col.property] || ''} onChange={this.createFilterHandler(col.property)} />}
</TableCell>
);
}, this)}
diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx
index 59dc49c50..96bcbf375 100644
--- a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx
+++ b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx
@@ -130,6 +130,7 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps,
)}
<Tooltip title="Actions">
<IconButton color="inherit"
+ aria-label={buttonPrefix +"additional-actions-button"}
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu} >
@@ -138,7 +139,7 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps,
</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>
+ <MenuItem aria-label="export-table-as-csv" onClick={(e) =>{ this.props.onExportToCsv(); this.handleClose()}}>Export as CSV</MenuItem>
</Menu>
</div>
</Toolbar>