diff --git a/.eslintrc.js b/.eslintrc.js
index 35b2a77f..da964bb6 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -159,7 +159,7 @@ module.exports = {
"no-caller": "error",
"no-cond-assign": "error",
"no-console": "off",
- "no-debugger": "error",
+ "no-debugger": "warn",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-empty": "error",
diff --git a/package.json b/package.json
index 41409b56..67e4e47a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ka-table",
- "version": "8.6.0",
+ "version": "8.7.0",
"license": "MIT",
"repository": "github:komarovalexander/ka-table",
"homepage": "https://komarovalexander.github.io/ka-table/#/overview",
diff --git a/src/Demos/Demos.tsx b/src/Demos/Demos.tsx
index dfb90915..a895be05 100644
--- a/src/Demos/Demos.tsx
+++ b/src/Demos/Demos.tsx
@@ -40,6 +40,7 @@ import GroupingCustomRowDemo from './GroupingCustomRowDemo/GroupingCustomRowDemo
import GroupingDemo from './GroupingDemo/GroupingDemo';
import GroupingSummaryDemo from './GroupingSummaryDemo/GroupingSummaryDemo';
import HeaderFilterDemo from './HeaderFilterDemo/HeaderFilterDemo';
+import HeaderFilterLogicDemo from './HeaderFilterLogicDemo/HeaderFilterLogicDemo';
import HoverRowDemo from './HoverRowDemo/HoverRowDemo';
import InfiniteScrollingDemo from './InfiniteScrollingDemo/InfiniteScrollingDemo';
import InsertRowDemo from './InsertRowDemo/InsertRowDemo';
@@ -113,6 +114,7 @@ const demos: Demo[] = [
new Demo(GroupingCustomRowDemo, '/grouping-custom-row', 'Grouping Custom Row', 'GroupingCustomRowDemo', 'https://stackblitz.com/edit/table-grouping-custom-row-js', 'https://stackblitz.com/edit/table-grouping-custom-row-ts', 'Grouping'),
new Demo(GroupingDemo, '/grouping', 'Grouping', 'GroupingDemo', 'https://stackblitz.com/edit/table-grouping-js', 'https://stackblitz.com/edit/table-grouping-ts', 'Grouping'),
new Demo(HeaderFilterDemo, '/header-filter', 'Header Filter', 'HeaderFilterDemo', 'https://stackblitz.com/edit/table-header-filter-js', 'https://stackblitz.com/edit/table-header-filter-ts', 'Filtering'),
+ new Demo(HeaderFilterLogicDemo, '/header-filter-logic', 'Header Filter - Logic', 'HeaderFilterLogicDemo', 'https://stackblitz.com/edit/table-header-filter-logic-js', 'https://stackblitz.com/edit/table-header-filter-logic-ts', 'Filtering'),
new Demo(GroupingSummaryDemo, '/grouping-summary', 'Grouping Summary', 'GroupingSummaryDemo', 'https://stackblitz.com/edit/table-grouping-summary-js', 'https://stackblitz.com/edit/table-grouping-summary-ts', 'Grouping'),
new Demo(HoverRowDemo, '/hover-row', 'Hover Row', 'HoverRowDemo', 'https://stackblitz.com/edit/table-hover-row-js', 'https://stackblitz.com/edit/table-hover-row-ts', 'Rows'),
new Demo(JsonDemo, '/json', 'Json', 'JsonDemo', 'https://stackblitz.com/edit/table-json-js', 'https://stackblitz.com/edit/table-json-ts', 'Remote Data'),
diff --git a/src/Demos/FilterRowCustomLogicDemo/FilterRowCustomLogicDemo.tsx b/src/Demos/FilterRowCustomLogicDemo/FilterRowCustomLogicDemo.tsx
index 64eff5be..73deb258 100644
--- a/src/Demos/FilterRowCustomLogicDemo/FilterRowCustomLogicDemo.tsx
+++ b/src/Demos/FilterRowCustomLogicDemo/FilterRowCustomLogicDemo.tsx
@@ -1,8 +1,8 @@
-import React from 'react';
-
import { DataType, Table } from '../../lib';
+
import FilterRowNumber from '../../lib/Components/FilterRowNumber/FilterRowNumber';
import { FilteringMode } from '../../lib/enums';
+import React from 'react';
const dataArray: any[] = [
{ id: 1, name: 'Mike Wazowski', score: 80, prevScores: [60, 65, 70], passed: true, nextTry: new Date(2021, 10, 9) },
@@ -42,6 +42,7 @@ const FilterRowCustomLogicDemo: React.FC = () => {
filterRowValue: 60,
key: 'prevScores',
style: {width: 120},
+ filter: (value: number[], filterRowValue: number) => value.includes(Number(filterRowValue)),
title: 'Previous Scores',
}
]}
@@ -51,11 +52,6 @@ const FilterRowCustomLogicDemo: React.FC = () => {
return value.join();
}
}}
- filter= {({ column }) => {
- if (column.key === 'prevScores') {
- return (value: number[], filterRowValue: number) => value.includes(Number(filterRowValue));
- }
- }}
filteringMode={FilteringMode.FilterRow}
rowKeyField={'id'}
childComponents={{
diff --git a/src/Demos/HeaderFilterLogicDemo/HeaderFilterLogicDemo.test.tsx b/src/Demos/HeaderFilterLogicDemo/HeaderFilterLogicDemo.test.tsx
new file mode 100644
index 00000000..53b90647
--- /dev/null
+++ b/src/Demos/HeaderFilterLogicDemo/HeaderFilterLogicDemo.test.tsx
@@ -0,0 +1,10 @@
+import HeaderFilterLogicDemo from './HeaderFilterLogicDemo';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ const root = createRoot(div!);
+ root.render();
+ root.unmount();
+});
diff --git a/src/Demos/HeaderFilterLogicDemo/HeaderFilterLogicDemo.tsx b/src/Demos/HeaderFilterLogicDemo/HeaderFilterLogicDemo.tsx
new file mode 100644
index 00000000..65a6ef26
--- /dev/null
+++ b/src/Demos/HeaderFilterLogicDemo/HeaderFilterLogicDemo.tsx
@@ -0,0 +1,115 @@
+import { DataType, Table } from '../../lib';
+import { FilteringMode, SortDirection, SortingMode } from '../../lib/enums';
+
+import React from 'react';
+
+const dataArray: any[] = [
+ {
+ id: 1,
+ name: 'Mike Wazowski',
+ score: 80,
+ passed: true,
+ nextTry: new Date(2021, 10, 8),
+ departments: [{ name: 'Department A', id: 1 }, { name: 'Department B', id: 2 }]
+ },
+ {
+ id: 2,
+ name: 'Billi Bob',
+ score: 55,
+ passed: false,
+ nextTry: new Date(2021, 10, 8),
+ departments: [{ name: 'Department C', id: 3 }]
+ },
+ {
+ id: 3,
+ name: 'Tom Williams',
+ score: 55,
+ passed: false,
+ nextTry: new Date(2021, 11, 8),
+ departments: [{ name: 'Department A', id: 1 }]
+ },
+ {
+ id: 4,
+ name: 'Kurt Cobain',
+ score: 75,
+ passed: true,
+ nextTry: new Date(2021, 12, 9)
+ },
+ {
+ id: 5,
+ name: 'Marshall Bruce',
+ score: 77,
+ passed: true,
+ nextTry: new Date(2021, 11, 12)
+ },
+ {
+ id: 6,
+ name: 'Sunny Fox',
+ score: 33,
+ passed: false,
+ nextTry: new Date(2021, 10, 9)
+ },
+];
+
+const HeaderFilterLogicDemo = () => {
+ return (
+
{
+ return filterValues?.some(x => value?.some(v => v.id === x.id));
+ },
+ headerFilterListItems: ({ data }) => {
+ const departments: any[] | undefined = data?.reduce((acc, item) => [...acc, ...item.departments || []], []);
+ const departmentsUniqueByKey = departments?.filter((item: any, index) => {
+ return departments?.findIndex(i => i.id === item.id) === index;
+ });
+ return departmentsUniqueByKey || [];
+ }
+ },
+ ]}
+ data={dataArray}
+ sortingMode={SortingMode.Single}
+ filteringMode={FilteringMode.HeaderFilter}
+ format={({ column, value }) => {
+ if (column.dataType === DataType.Date) {
+ return value && value.toLocaleDateString('en', { month: '2-digit', day: '2-digit', year: 'numeric' });
+ }
+ if (column.key === 'departments') {
+ return value?.map((d: any) => d.name).join(', ');
+ }
+ }}
+ childComponents={{
+ popupContentItemText: {
+ content: ({ item }) => item.name
+ }
+ }}
+ rowKeyField={'id'}
+ />
+ );
+};
+
+export default HeaderFilterLogicDemo;
diff --git a/src/Demos/MenuItems.tsx b/src/Demos/MenuItems.tsx
index f22dfce0..b80fe74b 100644
--- a/src/Demos/MenuItems.tsx
+++ b/src/Demos/MenuItems.tsx
@@ -14,8 +14,8 @@ export class MenuItem {
public isActive?: boolean;
}
-const newItems: string[] = [];
-const updateItems: string[] = ['GroupingDemo'];
+const newItems: string[] = ['HeaderFilterLogicDemo'];
+const updateItems: string[] = [];
const MenuItems: React.FC<{ items: MenuItem[] }> = ({ items }) => {
diff --git a/src/lib/Components/PopupContent/PopupContent.tsx b/src/lib/Components/PopupContent/PopupContent.tsx
index 4417a8ad..b003b0bf 100644
--- a/src/lib/Components/PopupContent/PopupContent.tsx
+++ b/src/lib/Components/PopupContent/PopupContent.tsx
@@ -14,23 +14,25 @@ const PopupContent: React.FC = (props) => {
dispatch,
format
} = props;
-
- let headerFilterValues = data?.map((item) => {
- const value = getValueByColumn(item, column);
-
- const formattedValue =
- (format && format({ column, value, rowData: item }))
- || value?.toString();
- return formattedValue;
- });
-
- headerFilterValues = Array.from(new Set(headerFilterValues));
+ let headerFilterValues: any[] | undefined;
+ if (column?.headerFilterListItems){
+ headerFilterValues = column?.headerFilterListItems({ data });
+ } else {
+ headerFilterValues = data?.map((item) => {
+ const value = getValueByColumn(item, column);
+
+ const formattedValue =
+ (format && format({ column, value, rowData: item }))
+ || value?.toString();
+ return formattedValue;
+ });
+ headerFilterValues = Array.from(new Set(headerFilterValues));
+ }
const { elementAttributes, content } = getElementCustomization({
className: `${defaultOptions.css.popupContent}`
}, props, childComponents?.popupContent
);
-
return (
{content ||
diff --git a/src/lib/Components/PopupContentItem/PopupContentItem.tsx b/src/lib/Components/PopupContentItem/PopupContentItem.tsx
index b00100b9..a5528f88 100644
--- a/src/lib/Components/PopupContentItem/PopupContentItem.tsx
+++ b/src/lib/Components/PopupContentItem/PopupContentItem.tsx
@@ -1,9 +1,10 @@
import * as React from 'react';
-import { updateHeaderFilterValues } from '../../actionCreators';
-import defaultOptions from '../../defaultOptions';
import { IPopupContentItemProps } from '../../props';
+import PopupContentItemText from '../PopupContentItemText/PopupContentItemText';
+import defaultOptions from '../../defaultOptions';
import { getElementCustomization } from '../../Utils/ComponentUtils';
+import { updateHeaderFilterValues } from '../../actionCreators';
const PopupContentItem: React.FC = (props) => {
const {
@@ -35,9 +36,7 @@ const PopupContentItem: React.FC = (props) => {
onChange={handleChange}
/>
-
- {item}
-
+
>
)}
diff --git a/src/lib/Components/PopupContentItemText/PopupContentItemText.test.tsx b/src/lib/Components/PopupContentItemText/PopupContentItemText.test.tsx
new file mode 100644
index 00000000..3de2ba23
--- /dev/null
+++ b/src/lib/Components/PopupContentItemText/PopupContentItemText.test.tsx
@@ -0,0 +1,17 @@
+import PopupContentItemText from './PopupContentItemText';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+
+const props: any = {
+ column: {key: 'field'},
+ childComponents: {},
+ dispatch: () => {},
+ item: ''
+};
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ const root = createRoot(div!);
+ root.render();
+ root.unmount();
+});
diff --git a/src/lib/Components/PopupContentItemText/PopupContentItemText.tsx b/src/lib/Components/PopupContentItemText/PopupContentItemText.tsx
new file mode 100644
index 00000000..88b43b9b
--- /dev/null
+++ b/src/lib/Components/PopupContentItemText/PopupContentItemText.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+
+import { IPopupContentItemProps } from '../../props';
+import { getElementCustomization } from '../../Utils/ComponentUtils';
+
+const PopupContentItemText: React.FC = (props) => {
+ const {
+ childComponents,
+ item
+ } = props;
+
+ const { elementAttributes, content } = getElementCustomization({
+ className: 'ka-popup-content-item-value'
+ }, props, childComponents?.popupContentItemText);
+
+ return (
+
+ {content || item.toString()}
+
+ )
+}
+
+export default PopupContentItemText;
diff --git a/src/lib/Models/ChildComponents.ts b/src/lib/Models/ChildComponents.ts
index 496dd0ef..66d022ab 100644
--- a/src/lib/Models/ChildComponents.ts
+++ b/src/lib/Models/ChildComponents.ts
@@ -64,6 +64,7 @@ export class ChildComponents {
public pagingSizes?: ChildComponent;
public popupContent?: ChildComponent;
public popupContentItem?: ChildComponent;
+ public popupContentItemText?: ChildComponent;
public rootDiv?: ChildComponent;
public sortIcon?: ChildComponent;
public summaryCell?: ChildComponent;
diff --git a/src/lib/Models/Column.ts b/src/lib/Models/Column.ts
index 553c8c17..cf097fc8 100644
--- a/src/lib/Models/Column.ts
+++ b/src/lib/Models/Column.ts
@@ -11,6 +11,8 @@ export class Column {
public filterRowValue?: any;
public headerFilterValues?: any[];
public headerFilterPopupPosition?: PopupPosition;
+ public headerFilterListItems?: (props: { data?: any[] }) => any[];
+ public filter?: (value: any, filterValue: any, rowData?: any) => boolean;
public isHeaderFilterPopupShown?: boolean;
public isEditable?: boolean;
public isFilterable?: boolean;
diff --git a/src/lib/Utils/FilterUtils.test.ts b/src/lib/Utils/FilterUtils.test.ts
index cba390ba..5246cb2a 100644
--- a/src/lib/Utils/FilterUtils.test.ts
+++ b/src/lib/Utils/FilterUtils.test.ts
@@ -133,6 +133,17 @@ describe('FilterUtils', () => {
}];
expect(() => filterData(data, columns)).toThrowError('\'unknownOperator\' has not found in predefinedFilterOperators array, available operators: =, >, <, >=, <=, contains');
});
+
+ it('custom column filter', () => {
+ const columns = [{
+ dataType: DataType.Number,
+ filterRowValue: 45,
+ key: 'score',
+ filter: (value: any, filterValue: any) => value === filterValue,
+ }];
+ const result = filterData(data, columns);
+ expect(result).toMatchSnapshot();
+ });
});
[{
diff --git a/src/lib/Utils/FilterUtils.ts b/src/lib/Utils/FilterUtils.ts
index 66cb5886..c53a62c8 100644
--- a/src/lib/Utils/FilterUtils.ts
+++ b/src/lib/Utils/FilterUtils.ts
@@ -59,7 +59,7 @@ export const filterAndSearchData = (props: ITableProps) => {
const getCompare = (column: Column) => {
const filterRowOperator = column.filterRowOperator
- || getDefaultOperatorForType(column.dataType || defaultOptions.columnDataType);
+ || getDefaultOperatorForType(column.dataType || defaultOptions.columnDataType);
const filterOperator = predefinedFilterOperators.find((fo) => filterRowOperator === fo.name);
if (!filterOperator) {
throw new Error(`'${column.filterRowOperator}' has not found in predefinedFilterOperators array, available operators: ${predefinedFilterOperators.map((o) => o.name).join(', ')}`);
@@ -71,12 +71,12 @@ export const filterData = (data: any[], columns: Column[], filter?: FilterFunc):
return columns.reduce((initialData, column) => {
if (
isEmpty(column.filterRowValue)
- && column.filterRowOperator !== FilterOperatorName.IsEmpty
- && column.filterRowOperator !== FilterOperatorName.IsNotEmpty
+ && column.filterRowOperator !== FilterOperatorName.IsEmpty
+ && column.filterRowOperator !== FilterOperatorName.IsNotEmpty
) {
return initialData;
}
- const customFilter = filter?.({ column });
+ const customFilter = column.filter || filter?.({ column });
const compare = customFilter || getCompare(column);
return initialData.filter((d: any) => {
@@ -136,16 +136,19 @@ export const filterByHeaderFilter = (data: any[], columns: Column[], format?: Fo
return columns.reduce((initialData, column) => {
if (
isEmpty(column.headerFilterValues)
- && column.filterRowOperator !== FilterOperatorName.IsEmpty
- && column.filterRowOperator !== FilterOperatorName.IsNotEmpty
+ && column.filterRowOperator !== FilterOperatorName.IsEmpty
+ && column.filterRowOperator !== FilterOperatorName.IsNotEmpty
) {
return initialData;
}
return initialData.filter((item: any) => {
const value: any = getValueByColumn(item, column);
+ if (column?.filter) {
+ return column.filter(value, column.headerFilterValues, item);
+ }
const fieldValue =
- (format && format({ column, value, rowData: item }))
- || value?.toString();
+ (format && format({ column, value, rowData: item }))
+ || value?.toString();
return column.headerFilterValues?.includes(fieldValue);
});
}, data);
diff --git a/src/lib/Utils/__snapshots__/FilterUtils.test.ts.snap b/src/lib/Utils/__snapshots__/FilterUtils.test.ts.snap
index 08b2872b..058f0a93 100644
--- a/src/lib/Utils/__snapshots__/FilterUtils.test.ts.snap
+++ b/src/lib/Utils/__snapshots__/FilterUtils.test.ts.snap
@@ -71,6 +71,18 @@ Array [
]
`;
+exports[`FilterUtils filterData custom column filter 1`] = `
+Array [
+ Object {
+ "date": 2021-11-20T13:00:00.000Z,
+ "id": 3,
+ "name": "Tom Williams",
+ "passed": false,
+ "score": 45,
+ },
+]
+`;
+
exports[`FilterUtils filterData custom filter 1`] = `
Array [
Object {