From 93e61ea4bf3cf0e2b57dd16ae5a9730c3dd6e1e4 Mon Sep 17 00:00:00 2001 From: Melloware Date: Mon, 5 Feb 2024 13:36:00 -0500 Subject: [PATCH] Fix #3325: Datatable custom filter demo (#5902) --- components/doc/datatable/filter/customdoc.js | 706 +++++++++++++++++++ pages/datatable/index.js | 6 + 2 files changed, 712 insertions(+) create mode 100644 components/doc/datatable/filter/customdoc.js 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 ( +
+ flag + {rowData.country.name} +
+ ); + }; + + const representativeBodyTemplate = (rowData) => { + const representative = rowData.representative; + + return ( +
+ {representative.name} + {representative.name} +
+ ); + }; + + const representativesItemTemplate = (option) => { + return ( +
+ {option.name} + {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 ( +
+ flag + {rowData.country.name} +
+ ); + }; + + const representativeBodyTemplate = (rowData) => { + const representative = rowData.representative; + + return ( +
+ {representative.name} + {representative.name} +
+ ); + }; + + const representativesItemTemplate = (option) => { + return ( +
+ {option.name} + {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 ( +
+ flag + {rowData.country.name} +
+ ); + }; + + const representativeBodyTemplate = (rowData: Customer) => { + const representative = rowData.representative; + + return ( +
+ {representative.name} + {representative.name} +
+ ); + }; + + const representativesItemTemplate = (option: Representative) => { + return ( +
+ {option.name} + {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 } ] },