primefaces / primevue

Next Generation Vue UI Component Library
https://primevue.org
MIT License
10.42k stars 1.23k forks source link

Data Table (Column Filter): Filtering on empty values #4242

Open mrichar1 opened 1 year ago

mrichar1 commented 1 year ago

Describe the feature you would like to see added

At present there doesn't appear to be a way to filter on empty string/value. It is therefore impossible to filter e.g. 'all rows where this column has an empty value'.

When opening the filter dialog, the input box is empty - but there is no distinction between this being an empty svalue versus an unset filter. Clicking 'Apply' fires an event, but the resulting filter constraint has no value defined, and the filter icon style doesn't update to show that it is active.

Is your feature request related to a problem?

It looks like the code doesn't distinguish between the filter constraint value being 'falsey' and being undefined/null.

For example:

filters: {
          name: { operator: 'and', constraints: [{ matchMode: 'equals', value: '' }] },
}

appears functionally equivalent to:

filters: {
          name: { operator: 'and', constraints: [{ matchMode: 'equals' }] },
}

(The latter is what is contained in filters if the input is empty when the 'Apply' button is pushed).

Describe the solution you'd like

One solution could be to make the absence of the value property, or value = null mean that a filter is unset; while value = '' means to filter on an empty value.

There would ideally be some other indicator/toggle in the filter dialog to make the filter is active. This might be useful anyway - for example you could define a complex filter, then turn it on/off without having to clear/recreate it.

Describe alternatives you have considered

It might be possible in a specific project with lazy enabled to translate various combinations of matchMode and undefined value into generating an 'empty' API query, but this is not a general solution.

Additional context

This is loosely related to #1669 and #1835 which are also about the processing of filter constraints.

trevormiles commented 8 months ago

I would also like to see a solution implemented for this. Needing to filter by empty/null seems like a common use case that isn't presently accounted for.

msnyder424 commented 6 months ago

I've been thinking about this for a while but haven't gotten around to messing with it. I think one should be able to register a custom filter with the filter service (see this related github issue: https://github.com/primefaces/primevue/issues/1343). To get around the falsiness of an empty string, one could use "null" to search for null values with a custom filter function, but this would not work on columns with any form input except InputText (e.g., InputNumber columns only accept numerical input). Maybe if you are using the menu style input (https://primevue.org/datatable/#advanced_filter), you could have a method in the match mode drop down that is something like "is null", which interprets an empty input differently? I am planning to get to this in an upcoming sprint and so was searching to see if there were any new solutions since the last time I looked. I'll let you know what transpires. Sorry, for the non-answer!

msnyder424 commented 4 months ago

I'm still trying to implement this. No luck yet, but I found this related self answered question on stack overflow.

https://stackoverflow.com/a/76392093/5692653

I created the simple filter below, but cannot get it to show up in the drop down menu.

import { usePrimeVue } from 'primevue/config';
import { FilterService } from 'primevue/api';

const PrimeVue = usePrimeVue();

PrimeVue.config.filterMatchModeOptions.text.push("IsNull");
PrimeVue.config.filterMatchModeOptions.numeric.push("IsNull");

FilterService.register('IsNull', (value, filter) => {
  return (filter || !filter) && (value === undefined || value === null)
});
hertelukas commented 3 months ago

Hi @msnyder424,

Not sure if that helps, but I was able to add the options this way:

const TAGS_FILTER = ref("TAG_FILTER")

onMounted(() => {
    FilterService.register(TAGS_FILTER.value, (value, filter): boolean => {
        console.log("Hi from my filter");
        return false;
    });
});

// Here you could also set more options and filters
const tagsContainMatchModes = [{ label: "Filter tag", value: TAGS_FILTER.value }] 

const initFilters = () => {
    filters.value = {
        global: { value: null, matchMode: FilterMatchMode.CONTAINS },
        tags: { value: null, matchMode: TAGS_FILTER.value }
    };
};

initFilters();

And then, in the Column (see :filter-match-mode-options):

        <Column field="tags" header="Tags" :filter-match-mode-options="tagsContainMatchModes">
            <template #body="{ data }">
                <div class="flex flex-wrap gap-2">
                    <Tag severity="success" v-for="tag in data.tags">{{ tag.tag }}</Tag>
                </div>
            </template>
            <template #filter="{ filterModel }">
                <InputText v-model="filterModel.value" type="text" placeholder="Filter by tag" />
            </template>
        </Column>

This allows the selection of my filter, but when I apply a filter I get Unhandled Promise Rejection: TypeError: filterConstraint2 is not a function. (In 'filterConstraint2(dataFieldValue, filterValue, this.filterLocale)', 'filterConstraint2' is undefined), which seems to be an unrelated problem. (Okay, seems to be fixed when updating to the newest version of primevue/core)