From 41af0f9a1ffddc8ce5fed6d77d8c915383df46c2 Mon Sep 17 00:00:00 2001 From: Petr Kachanovsky Date: Tue, 4 Feb 2025 13:09:13 +0200 Subject: [PATCH] feat: add filterOptions field to columns --- .../13-standardPagesTuning.md | 35 +++++++++++++++++- adminforth/modules/configValidator.ts | 24 +++++++++++++ adminforth/spa/src/components/Filters.vue | 36 +++++++++++++------ adminforth/types/Common.ts | 12 +++++++ 4 files changed, 95 insertions(+), 12 deletions(-) diff --git a/adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md b/adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md index 38fc21af8..325b7a323 100644 --- a/adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md +++ b/adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md @@ -491,4 +491,37 @@ export default { ], ``` -This way, when creating or editing a record you will be able to choose value for this field from a dropdown selector and on list and show pages this field will be displayed as a link to a foreign resource. \ No newline at end of file +This way, when creating or editing a record you will be able to choose value for this field from a dropdown selector and on list and show pages this field will be displayed as a link to a foreign resource. + +## Filtering + +### Filter Options + +You can specify the delay between filtering requests and filtering operator for a column using `filterOptions` field. + +```typescript title="./resources/adminuser.ts" +export default { + name: 'adminuser', + columns: [ + ... + { + name: "title", + required: true, + maxLength: 255, + minLength: 3, +//diff-add + filterOptions: { +//diff-add + debounceTimeMs: 500, +//diff-add + substringSearch: false, +//diff-add + }, + }, + ], + }, + ... + ], +``` +`debounceTimeMs` field dictates how long (in milliseconds) to wait between inputs to send updated data request. By increasing this value, you can reduce the amount of requests set to backend. Default value for this field is set to 10ms. +`substringSearch` sets what comparison operator to use for text field. By default this field is set to `true`, which results in using case-insensitive `ILIKE` operator, that will look for records that have filter string anywhere inside field value. Setting this `substringSearch` to `false` will result in using more strict `EQ` operator, that will look for exact full-string matches. diff --git a/adminforth/modules/configValidator.ts b/adminforth/modules/configValidator.ts index e35609a49..ed15f6267 100644 --- a/adminforth/modules/configValidator.ts +++ b/adminforth/modules/configValidator.ts @@ -374,6 +374,30 @@ export default class ConfigValidator implements IConfigValidator { //define default sortable if (!Object.keys(col).includes('sortable')) { col.sortable = !col.virtual; } + // define default filter options + if (!Object.keys(col).includes('filterOptions')) { + col.filterOptions = { + debounceTimeMs: 10, + substringSearch: true, + }; + } else { + if (col.filterOptions.debounceTimeMs !== undefined) { + if (typeof col.filterOptions.debounceTimeMs !== 'number') { + errors.push(`Resource "${res.resourceId}" column "${col.name}" filterOptions.debounceTimeMs must be a number`); + } + } else { + col.filterOptions.debounceTimeMs = 10; + } + + if (col.filterOptions.substringSearch !== undefined) { + if (typeof col.filterOptions.substringSearch !== 'boolean') { + errors.push(`Resource "${res.resourceId}" column "${col.name}" filterOptions.substringSearch must be a boolean`); + } + } else { + col.filterOptions.substringSearch = true; + } + } + col.showIn = this.validateAndNormalizeShowIn(resInput, inCol, errors, warnings); // check col.required is boolean or object diff --git a/adminforth/spa/src/components/Filters.vue b/adminforth/spa/src/components/Filters.vue index 6ca404d2e..338a3545b 100644 --- a/adminforth/spa/src/components/Filters.vue +++ b/adminforth/spa/src/components/Filters.vue @@ -27,7 +27,7 @@ multiple class="w-full" :options="columnOptions[c.name] || []" - @update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event.length ? $event : undefined })" + @update:modelValue="onFilterInput[c.name]({ column: c, operator: 'in', value: $event.length ? $event : undefined })" :modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []" /> @@ -127,6 +127,7 @@ import CustomRangePicker from "@/components/CustomRangePicker.vue"; import { useFiltersStore } from '@/stores/filters'; import Input from '@/afcl/Input.vue'; import Select from '@/afcl/Select.vue'; +import debounce from 'debounce'; const filtersStore = useFiltersStore(); @@ -186,6 +187,19 @@ watch(() => props.show, (show) => { // operator: 'like' // } +const onFilterInput = computed(() => { + if (!props.columns) return {}; + + return props.columns.reduce((acc, c) => { + return { + ...acc, + [c.name]: debounce(({ column, operator, value }) => { + setFilterItem({ column, operator, value }); + }, c.filterOptions?.debounceTimeMs || 10), + }; + }, {}); +}); + function setFilterItem({ column, operator, value }) { const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator); diff --git a/adminforth/types/Common.ts b/adminforth/types/Common.ts index d6150b617..aae542e1c 100644 --- a/adminforth/types/Common.ts +++ b/adminforth/types/Common.ts @@ -784,6 +784,18 @@ export interface AdminForthResourceColumnInputCommon { sortable?: boolean, + + filterOptions?: { + /** + * Decrease number of requests by adding debounce time to filter requests. + */ + debounceTimeMs?: number, + /** + * If true - will force EQ operator for filter instead of ILIKE. + */ + substringSearch?: boolean, + }, + /** * if true field will !not be passed to UI under no circumstances, but will be presented in hooks */