diff --git a/components/doc/datatable/filter/customdoc.js b/components/doc/datatable/filter/customdoc.js
new file mode 100644
index 0000000000..1581286bba
--- /dev/null
+++ b/components/doc/datatable/filter/customdoc.js
@@ -0,0 +1,706 @@
+import DeferredDemo from '@/components/demo/DeferredDemo';
+import { DocSectionCode } from '@/components/doc/common/docsectioncode';
+import { DocSectionText } from '@/components/doc/common/docsectiontext';
+import { FilterMatchMode, FilterService } from '@/components/lib/api/Api';
+import { Column } from '@/components/lib/column/Column';
+import { DataTable } from '@/components/lib/datatable/DataTable';
+import { Dropdown } from '@/components/lib/dropdown/Dropdown';
+import { InputNumber } from '@/components/lib/inputnumber/InputNumber';
+import { InputText } from '@/components/lib/inputtext/InputText';
+import { MultiSelect } from '@/components/lib/multiselect/MultiSelect';
+import { Tag } from '@/components/lib/tag/Tag';
+import { TriStateCheckbox } from '@/components/lib/tristatecheckbox/TriStateCheckbox';
+import { classNames } from '@/components/lib/utils/Utils';
+import { useState } from 'react';
+import { CustomerService } from '../../../../service/CustomerService';
+
+// The rule argument should be a string in the format "custom_[field]".
+FilterService.register('custom_activity', (value, filters) => {
+ const [from, to] = filters ?? [null, null];
+
+ if (from === null && to === null) return true;
+ if (from !== null && to === null) return from <= value;
+ if (from === null && to !== null) return value <= to;
+
+ return from <= value && value <= to;
+});
+
+export function CustomFilterDoc(props) {
+ const [customers, setCustomers] = useState(null);
+ const [filters, setFilters] = useState({
+ global: { value: null, matchMode: FilterMatchMode.CONTAINS },
+ name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
+ 'country.name': { value: null, matchMode: FilterMatchMode.STARTS_WITH },
+ representative: { value: null, matchMode: FilterMatchMode.IN },
+ // For using custom filters, you must set FilterMatchMode.CUSTOM to matchMode.
+ activity: { value: null, matchMode: FilterMatchMode.CUSTOM },
+ status: { value: null, matchMode: FilterMatchMode.EQUALS },
+ verified: { value: null, matchMode: FilterMatchMode.EQUALS }
+ });
+ const [loading, setLoading] = useState(true);
+ const [globalFilterValue, setGlobalFilterValue] = useState('');
+ const [representatives] = useState([
+ { name: 'Amy Elsner', image: 'amyelsner.png' },
+ { name: 'Anna Fali', image: 'annafali.png' },
+ { name: 'Asiya Javayant', image: 'asiyajavayant.png' },
+ { name: 'Bernardo Dominic', image: 'bernardodominic.png' },
+ { name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
+ { name: 'Ioni Bowcher', image: 'ionibowcher.png' },
+ { name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
+ { name: 'Onyama Limba', image: 'onyamalimba.png' },
+ { name: 'Stephen Shaw', image: 'stephenshaw.png' },
+ { name: 'XuXue Feng', image: 'xuxuefeng.png' }
+ ]);
+ const [statuses] = useState(['unqualified', 'qualified', 'new', 'negotiation', 'renewal']);
+
+ const getSeverity = (status) => {
+ switch (status) {
+ case 'unqualified':
+ return 'danger';
+
+ case 'qualified':
+ return 'success';
+
+ case 'new':
+ return 'info';
+
+ case 'negotiation':
+ return 'warning';
+
+ case 'renewal':
+ return null;
+ }
+ };
+
+ const loadDemoData = () => {
+ CustomerService.getCustomersMedium().then((data) => {
+ setCustomers(getCustomers(data));
+ setLoading(false);
+ });
+ };
+
+ const getCustomers = (data) => {
+ return [...(data || [])].map((d) => {
+ d.date = new Date(d.date);
+
+ return d;
+ });
+ };
+
+ const onGlobalFilterChange = (e) => {
+ const value = e.target.value;
+ let _filters = { ...filters };
+
+ _filters['global'].value = value;
+
+ setFilters(_filters);
+ setGlobalFilterValue(value);
+ };
+
+ const renderHeader = () => {
+ return (
+
+
+
+
+
+
+ );
+ };
+
+ const countryBodyTemplate = (rowData) => {
+ return (
+
+
+
{rowData.country.name}
+
+ );
+ };
+
+ const representativeBodyTemplate = (rowData) => {
+ const representative = rowData.representative;
+
+ return (
+
+
+
{representative.name}
+
+ );
+ };
+
+ const representativesItemTemplate = (option) => {
+ return (
+
+
+
{option.name}
+
+ );
+ };
+
+ const statusBodyTemplate = (rowData) => {
+ return ;
+ };
+
+ const statusItemTemplate = (option) => {
+ return ;
+ };
+
+ const verifiedBodyTemplate = (rowData) => {
+ return ;
+ };
+
+ const representativeRowFilterTemplate = (options) => {
+ return (
+ options.filterApplyCallback(e.value)}
+ optionLabel="name"
+ placeholder="Any"
+ className="p-column-filter"
+ maxSelectedLabels={1}
+ style={{ minWidth: '14rem' }}
+ />
+ );
+ };
+
+ const statusRowFilterTemplate = (options) => {
+ return (
+ options.filterApplyCallback(e.value)} itemTemplate={statusItemTemplate} placeholder="Select One" className="p-column-filter" showClear style={{ minWidth: '12rem' }} />
+ );
+ };
+
+ const verifiedRowFilterTemplate = (options) => {
+ return options.filterApplyCallback(e.value)} />;
+ };
+
+ const activityRowFilterTemplate = (options) => {
+ const [from, to] = options.value ?? [null, null];
+
+ return (
+
+ options.filterApplyCallback([e.value, to])} className="w-full" placeholder="from" />
+ options.filterApplyCallback([from, e.value])} className="w-full" placeholder="to" />
+
+ );
+ };
+
+ const header = renderHeader();
+
+ const code = {
+ basic: `
+// The rule argument should be a string in the format "custom_[field]".
+FilterService.register('custom_activity', (value, filters) => {
+ const [from, to] = filters ?? [null, null];
+ if (from === null && to === null) return true;
+ if (from !== null && to === null) return from <= value;
+ if (from === null && to !== null) return value <= to;
+ return from <= value && value <= to;
+});
+
+
+
+
+
+
+
+
+
+ `,
+ javascript: `
+import React, { useState, useEffect } from 'react';
+import { classNames } from 'primereact/utils';
+import { FilterMatchMode, FilterService } from 'primereact/api';
+import { DataTable, DataTableFilterMeta } from 'primereact/datatable';
+import { Column, ColumnFilterElementTemplateOptions } from 'primereact/column';
+import { InputText } from 'primereact/inputtext';
+import { Dropdown, DropdownChangeEvent } from 'primereact/dropdown';
+import { MultiSelect, MultiSelectChangeEvent } from 'primereact/multiselect';
+import { Tag } from 'primereact/tag';
+import { TriStateCheckbox, TriStateCheckboxChangeEvent} from 'primereact/tristatecheckbox';
+import { CustomerService } from './service/CustomerService';
+import { InputNumber } from 'primereact/inputnumber';
+
+// The rule argument should be a string in the format "custom_[field]".
+FilterService.register('custom_activity', (value, filters) => {
+ const [from, to] = filters ?? [null, null];
+ if (from === null && to === null) return true;
+ if (from !== null && to === null) return from <= value;
+ if (from === null && to !== null) return value <= to;
+ return from <= value && value <= to;
+});
+
+export default function CustomFilterDemo() {
+ const [customers, setCustomers] = useState(null);
+ const [filters, setFilters] = useState({
+ global: { value: null, matchMode: FilterMatchMode.CONTAINS },
+ name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
+ 'country.name': { value: null, matchMode: FilterMatchMode.STARTS_WITH },
+ representative: { value: null, matchMode: FilterMatchMode.IN },
+ // For using custom filters, you must set FilterMatchMode.CUSTOM to matchMode.
+ activity: { value: null, matchMode: FilterMatchMode.CUSTOM },
+ status: { value: null, matchMode: FilterMatchMode.EQUALS },
+ verified: { value: null, matchMode: FilterMatchMode.EQUALS }
+ });
+ const [loading, setLoading] = useState(true);
+ const [globalFilterValue, setGlobalFilterValue] = useState('');
+ const [representatives] = useState([
+ { name: 'Amy Elsner', image: 'amyelsner.png' },
+ { name: 'Anna Fali', image: 'annafali.png' },
+ { name: 'Asiya Javayant', image: 'asiyajavayant.png' },
+ { name: 'Bernardo Dominic', image: 'bernardodominic.png' },
+ { name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
+ { name: 'Ioni Bowcher', image: 'ionibowcher.png' },
+ { name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
+ { name: 'Onyama Limba', image: 'onyamalimba.png' },
+ { name: 'Stephen Shaw', image: 'stephenshaw.png' },
+ { name: 'XuXue Feng', image: 'xuxuefeng.png' }
+ ]);
+ const [statuses] = useState(['unqualified', 'qualified', 'new', 'negotiation', 'renewal']);
+
+ const getSeverity = (status) => {
+ switch (status) {
+ case 'unqualified':
+ return 'danger';
+
+ case 'qualified':
+ return 'success';
+
+ case 'new':
+ return 'info';
+
+ case 'negotiation':
+ return 'warning';
+
+ case 'renewal':
+ return null;
+ }
+ };
+
+ useEffect(() => {
+ CustomerService.getCustomersMedium().then((data) => {
+ setCustomers(getCustomers(data));
+ setLoading(false);
+ });
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ const getCustomers = (data) => {
+ return [...(data || [])].map((d) => {
+ d.date = new Date(d.date);
+
+ return d;
+ });
+ };
+
+ const onGlobalFilterChange = (e) => {
+ const value = e.target.value;
+ let _filters = { ...filters };
+
+ _filters['global'].value = value;
+
+ setFilters(_filters);
+ setGlobalFilterValue(value);
+ };
+
+ const renderHeader = () => {
+ return (
+
+
+
+
+
+
+ );
+ };
+
+ const countryBodyTemplate = (rowData) => {
+ return (
+
+
+
{rowData.country.name}
+
+ );
+ };
+
+ const representativeBodyTemplate = (rowData) => {
+ const representative = rowData.representative;
+
+ return (
+
+
+
{representative.name}
+
+ );
+ };
+
+ const representativesItemTemplate = (option) => {
+ return (
+
+
+
{option.name}
+
+ );
+ };
+
+ const statusBodyTemplate = (rowData) => {
+ return ;
+ };
+
+ const statusItemTemplate = (option) => {
+ return ;
+ };
+
+ const verifiedBodyTemplate = (rowData) => {
+ return ;
+ };
+
+ const representativeRowFilterTemplate = (options) => {
+ return (
+ options.filterApplyCallback(e.value)}
+ optionLabel="name"
+ placeholder="Any"
+ className="p-column-filter"
+ maxSelectedLabels={1}
+ style={{ minWidth: '14rem' }}
+ />
+ );
+ };
+
+ const statusRowFilterTemplate = (options) => {
+ return (
+ options.filterApplyCallback(e.value)} itemTemplate={statusItemTemplate} placeholder="Select One" className="p-column-filter" showClear style={{ minWidth: '12rem' }} />
+ );
+ };
+
+ const verifiedRowFilterTemplate = (options) => {
+ return options.filterApplyCallback(e.value)} />;
+ };
+
+ const activityRowFilterTemplate = (options) => {
+ const [from, to] = options.value ?? [null, null];
+
+ return (
+
+ options.filterApplyCallback([e.value, to])} className="w-full" placeholder="from" />
+ options.filterApplyCallback([from, e.value])} className="w-full" placeholder="to" />
+
+ );
+ };
+
+ const header = renderHeader();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+ `,
+ typescript: `
+import React, { useState, useEffect } from 'react';
+import { classNames } from 'primereact/utils';
+import { FilterMatchMode, FilterService } from 'primereact/api';
+import { DataTable, DataTableFilterMeta } from 'primereact/datatable';
+import { Column, ColumnFilterElementTemplateOptions } from 'primereact/column';
+import { InputText } from 'primereact/inputtext';
+import { Dropdown, DropdownChangeEvent } from 'primereact/dropdown';
+import { MultiSelect, MultiSelectChangeEvent } from 'primereact/multiselect';
+import { Tag } from 'primereact/tag';
+import { TriStateCheckbox, TriStateCheckboxChangeEvent} from 'primereact/tristatecheckbox';
+import { CustomerService } from './service/CustomerService';
+import { InputNumber } from 'primereact/inputnumber';
+
+interface Representative {
+ name: string;
+ image: string;
+}
+
+interface Country {
+ name: string;
+ code: string;
+}
+
+interface Customer {
+ id: number;
+ name: string;
+ country: Country;
+ company: string;
+ date: string;
+ status: string;
+ verified: boolean;
+ activity: number;
+ representative: Representative;
+ balance: number;
+}
+
+// The rule argument should be a string in the format "custom_[field]".
+FilterService.register('custom_activity', (value, filters) => {
+ const [from, to] = filters ?? [null, null];
+ if (from === null && to === null) return true;
+ if (from !== null && to === null) return from <= value;
+ if (from === null && to !== null) return value <= to;
+ return from <= value && value <= to;
+});
+
+export default function CustomFilterDemo() {
+ const [customers, setCustomers] = useState([]);
+ const [filters, setFilters] = useState({
+ global: { value: null, matchMode: FilterMatchMode.CONTAINS },
+ name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
+ 'country.name': { value: null, matchMode: FilterMatchMode.STARTS_WITH },
+ representative: { value: null, matchMode: FilterMatchMode.IN },
+ // For using custom filters, you must set FilterMatchMode.CUSTOM to matchMode.
+ activity: { value: null, matchMode: FilterMatchMode.CUSTOM },
+ status: { value: null, matchMode: FilterMatchMode.EQUALS },
+ verified: { value: null, matchMode: FilterMatchMode.EQUALS }
+ });
+ const [loading, setLoading] = useState(true);
+ const [globalFilterValue, setGlobalFilterValue] = useState('');
+ const [representatives] = useState([
+ { name: 'Amy Elsner', image: 'amyelsner.png' },
+ { name: 'Anna Fali', image: 'annafali.png' },
+ { name: 'Asiya Javayant', image: 'asiyajavayant.png' },
+ { name: 'Bernardo Dominic', image: 'bernardodominic.png' },
+ { name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
+ { name: 'Ioni Bowcher', image: 'ionibowcher.png' },
+ { name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
+ { name: 'Onyama Limba', image: 'onyamalimba.png' },
+ { name: 'Stephen Shaw', image: 'stephenshaw.png' },
+ { name: 'XuXue Feng', image: 'xuxuefeng.png' }
+ ]);
+ const [statuses] = useState(['unqualified', 'qualified', 'new', 'negotiation', 'renewal']);
+
+ const getSeverity = (status: string) => {
+ switch (status) {
+ case 'unqualified':
+ return 'danger';
+
+ case 'qualified':
+ return 'success';
+
+ case 'new':
+ return 'info';
+
+ case 'negotiation':
+ return 'warning';
+
+ case 'renewal':
+ return null;
+ }
+ };
+
+ useEffect(() => {
+ CustomerService.getCustomersMedium().then((data: Customer[]) => {
+ setCustomers(getCustomers(data));
+ setLoading(false);
+ });
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ const getCustomers = (data: Customer[]) => {
+ return [...(data || [])].map((d) => {
+ // @ts-ignore
+ d.date = new Date(d.date);
+
+ return d;
+ });
+ };
+
+ const onGlobalFilterChange = (e: React.ChangeEvent) => {
+ const value = e.target.value;
+ let _filters = { ...filters };
+
+ // @ts-ignore
+ _filters['global'].value = value;
+
+ setFilters(_filters);
+ setGlobalFilterValue(value);
+ };
+
+ const renderHeader = () => {
+ return (
+
+
+
+
+
+
+ );
+ };
+
+ const countryBodyTemplate = (rowData: Customer) => {
+ return (
+
+
+
{rowData.country.name}
+
+ );
+ };
+
+ const representativeBodyTemplate = (rowData: Customer) => {
+ const representative = rowData.representative;
+
+ return (
+
+
+
{representative.name}
+
+ );
+ };
+
+ const representativesItemTemplate = (option: Representative) => {
+ return (
+
+
+
{option.name}
+
+ );
+ };
+
+ const statusBodyTemplate = (rowData: Customer) => {
+ return ;
+ };
+
+ const statusItemTemplate = (option: string) => {
+ return ;
+ };
+
+ const verifiedBodyTemplate = (rowData: Customer) => {
+ return ;
+ };
+
+ const representativeRowFilterTemplate = (options: ColumnFilterElementTemplateOptions) => {
+ return (
+ options.filterApplyCallback(e.value)}
+ optionLabel="name"
+ placeholder="Any"
+ className="p-column-filter"
+ maxSelectedLabels={1}
+ style={{ minWidth: '14rem' }}
+ />
+ );
+ };
+
+ const statusRowFilterTemplate = (options: ColumnFilterElementTemplateOptions) => {
+ return (
+ options.filterApplyCallback(e.value)} itemTemplate={statusItemTemplate} placeholder="Select One" className="p-column-filter" showClear style={{ minWidth: '12rem' }} />
+ );
+ };
+
+ const verifiedRowFilterTemplate = (options: ColumnFilterElementTemplateOptions) => {
+ return options.filterApplyCallback(e.value)} />;
+ };
+
+ const activityRowFilterTemplate = (options) => {
+ const [from, to] = options.value ?? [null, null];
+
+ return (
+
+ options.filterApplyCallback([e.value, to])} className="w-full" placeholder="from" />
+ options.filterApplyCallback([from, e.value])} className="w-full" placeholder="to" />
+
+ );
+ };
+
+ const header = renderHeader();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+ `,
+ data: `
+{
+ id: 1000,
+ name: 'James Butt',
+ country: {
+ name: 'Algeria',
+ code: 'dz'
+ },
+ company: 'Benton, John B Jr',
+ date: '2015-09-13',
+ status: 'unqualified',
+ verified: true,
+ activity: 17,
+ representative: {
+ name: 'Ioni Bowcher',
+ image: 'ionibowcher.png'
+ },
+ balance: 70663
+},
+...
+ `
+ };
+
+ return (
+ <>
+
+
+ Custom filtering is enabled by defining a filter function using FilterService.register where the rule argument must be "custom_[field]". The "Activity" field in this example allows custom filtering by a range of two
+ values.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/pages/datatable/index.js b/pages/datatable/index.js
index 316c7513ca..08b74f26ae 100644
--- a/pages/datatable/index.js
+++ b/pages/datatable/index.js
@@ -18,6 +18,7 @@ import { RowEditDoc } from '@/components/doc/datatable/edit/roweditdoc';
import { ExportDoc } from '@/components/doc/datatable/exportdoc';
import { AdvancedFilterDoc } from '@/components/doc/datatable/filter/advanceddoc';
import { BasicFilterDoc } from '@/components/doc/datatable/filter/basicdoc';
+import { CustomFilterDoc } from '@/components/doc/datatable/filter/customdoc';
import { GridLinesDoc } from '@/components/doc/datatable/gridlinesdoc';
import { ImportDoc } from '@/components/doc/datatable/importdoc';
import { LazyLoadDoc } from '@/components/doc/datatable/lazyloaddoc';
@@ -148,6 +149,11 @@ const DataTableDemo = () => {
id: 'advanced_filter',
label: 'Advanced',
component: AdvancedFilterDoc
+ },
+ {
+ id: 'custom_filter',
+ label: 'Custom',
+ component: CustomFilterDoc
}
]
},