Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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.
24 changes: 24 additions & 0 deletions adminforth/modules/configValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 25 additions & 11 deletions adminforth/spa/src/components/Filters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 || []"
/>
<Select
Expand All @@ -40,7 +40,7 @@
// if field is not required, undefined might be there, and user might want to filter by it
...(c.required ? [] : [ { label: $t('Unset'), value: undefined } ])
]"
@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 || []"
/>

Expand All @@ -49,7 +49,7 @@
class="w-full"
v-else-if="c.enum"
:options="c.enum"
@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 || []"
/>

Expand All @@ -58,42 +58,42 @@
type="text"
full-width
:placeholder="$t('Search')"
@update:modelValue="setFilterItem({ column: c, operator: 'ilike', value: $event || undefined })"
:modelValue="getFilterItem({ column: c, operator: 'ilike' })"
@update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions?.substringSearch ? 'ilike' : 'eq', value: $event || undefined })"
:modelValue="getFilterItem({ column: c, operator: c.filterOptions?.substringSearch ? 'ilike' : 'eq' })"
/>

<CustomDateRangePicker
v-else-if="['datetime', 'date', 'time'].includes(c.type)"
:column="c"
:valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
@update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event || undefined })"
:valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
@update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event || undefined })"
/>

<CustomRangePicker
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
:min="getFilterMinValue(c.name)"
:max="getFilterMaxValue(c.name)"
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
@update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event || undefined })"
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
@update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event || undefined })"
/>

<div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
<Input
type="number"
aria-describedby="helper-text-explanation"
:placeholder="$t('From')"
@update:modelValue="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
@update:modelValue="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event || undefined })"
:modelValue="getFilterItem({ column: c, operator: 'gte' })"
/>
<Input
type="number"
aria-describedby="helper-text-explanation"
:placeholder="$t('To')"
@update:modelValue="setFilterItem({ column: c, operator: 'lte', value: $event|| undefined })"
@update:modelValue="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event|| undefined })"
:modelValue="getFilterItem({ column: c, operator: 'lte' })"
/>
</div>
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions adminforth/types/Common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down