How to override default filter operator?

1 Answer 790 Views
Grid
James
Top achievements
Rank 1
James asked on 28 Jan 2023, 06:39 PM | edited on 30 Jan 2023, 11:17 PM

Hello,

I am using the GridFilterMode.Menu option for my grid. I have a column which values are comma-delimited strings and I want to be able to search on this column using "contains" operator as opposed to the default "equals" operator. Does anyone know how I can achieve this? Can you point me to documentation?

Here is my grid column configuration:

columns.Bound(c => c.EntityList).Title("Items")
            .Filterable(f => f.Multi(true)
            .CheckAll(true)
            .ItemTemplate("itemTemplate")
            .Search(true)
            .DataSource(ds => ds.Read(r => r.Action("Entity_Distinct", "Admin")))
            )

Do I need to write custom javascript function to override the filter operator? I am trying to avoid making customizations to the out of the box control if possible.

I came across below Telerik article, however, I am not sure how to implement this for several columns with multi checkboxes enabled. I am running into issues with filter state active style and also how to manage "clear" filter functionality when several filters are active for these columns with multi checkboxes enabled..

https://docs.telerik.com/kendo-ui/knowledge-base/grid-how-to-change-multi-checkbox-filter-to-contains

UPDATE: I ended up writing  custom functions for filter and filtermenuopen events:

    .Events(e => e.Filter("onCategoryFilter")
    .FilterMenuOpen("onFilterMenuOpen")

 

function onFilterMenuOpen(e) {
        if (e.sender.dataSource.filter()) {
            {
                e.sender.dataSource.filter().filters.forEach(function (f) {
                    ///TODO check for f.filters; if it is not null then loop through filters collection and process each individual
                    //filter which contains field, operator value
                    if(f.filters)
                    {
                        f.filters.forEach(function (g) {
                            if (g.field == "Items" || g.field == "Subitems") {
                                // this checks the corresponding checkbox in filter options menu
                                if (e.field == g.field) {
                                    var checkbox = e.container.find("input[value='" + g.value + "']");
                                    if (!checkbox[0].checked) {
                                        e.container.find("input[value='" + g.value + "']").click()
                                    }
                                }
                            }
                        })
                    }
                    else if (f.field == "Items" || f.field == "Subitems") 
                    {
                            // this checks the corresponding checkbox in filter options menu
                            if(e.field == f.field)
                            {
                                var checkbox = e.container.find("input[value='" + f.value + "']");                    
                                if (!checkbox[0].checked) {                        
                                    e.container.find("input[value='" + f.value + "']").click()
                                }
                            }
                    }
                 })
            }
    }


function onCategoryFilter(e) {
        if ((e.field == "Items" && e.filter) || (e.field == "Subitems" && e.filter)) {
            e.filter.filters.forEach(function (f) {
                f.operator = "contains";
                console.log('using contains operator');
            });

            // Provide default logic operators ("Or") REVISIT Do I need this?
            /*
            dataSource.filter(
            {
                logic: "or",
                filters: filterCategories
            });
            */

            // Update User Interface by using Kendo classes.
            $("th[data-field='" + e.field + "'] a").first().addClass("k-active");
            $("th[data-field='" + e.field + "'] a").first().removeClass("k-border-down");

            // this checks the corresponding checkbox in filter options menu
            e.filter.filters.forEach(function (item, i) {
                $("input[name='" + e.field + "'][value='" + e.filter.filters[i].value + "']").prop("checked", true);
            });
        }
        else if (e.field == "Items" || e.field == "Subitems") {
            var grid = $("#grid").data("kendoGrid");
            var dataSource = grid.dataSource;
            if (dataSource.filter() != null) {
                filters = dataSource.filter().filters;
                if (filters.length > 0) {                    
                    removeFilter(filters, e.field, dataSource);
                    $("th[data-field='" + e.field + "'] a").first().addClass("k-border-down");
                    $("th[data-field='" + e.field + "'] a").first().removeClass("k-active");
                }

                dataSource.filter(filters);
                e.preventDefault();
            }
        }
    }

Is there any out of the box functionality that can be configured instead of writing custom code as it introduces inadvertent glitches and also I don't like the way I had to implement onFilterMenuOpen function where I had to have conditional logic to check whether or not the "f.filters" collection was null which is introducing code duplication. Any suggestions?

Thank you!

1 Answer, 1 is accepted

Sort by
0
Stoyan
Telerik team
answered on 01 Feb 2023, 03:41 PM

Hi James,

Thank you for sharing such detailed explanations and code snippets.


The approach that you have adapted to apply the "contains" operator to the MultiCheckbox filtering of the Grid's column is the official solution we suggest.

Unfortunately, at the current time there isn't an out-of-the-box approach to support the requirement.

However, when multiples columns are filtered you can avoid the need for code duplication by adding an additional check in the innermost if statement:

function onFilterMenuOpen(e) {
            if (e.sender.dataSource.filter()) {
                    e.sender.dataSource.filter().filters.forEach(function (f) {
                        ///TODO check for f.filters; if it is not null then loop through filters collection and process each individual
                        //filter which contains field, operator value
                                if (f.field == "Items" || f.field == "Subitems") {
                                    // this checks the corresponding checkbox in filter options menu
                                        var checkbox = e.container.find("input[value='" + f.value + "']");
                                        if (checkbox[0]!=undefined && !checkbox[0].checked) {
                                            e.container.find("input[value='" + f.value + "']").click()
                                        }
                                }
                         
                   })
                    
                }
            }

Please give this suggestion a try and let me know how it works on your side.

Regards,
Stoyan
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

James
Top achievements
Rank 1
commented on 02 Feb 2023, 09:22 PM | edited

Thank you for your reply, Stoyan. I modified the function as below based on your suggestion:

function onFilterMenuOpen(e) {
        console.log('on filter menu open');
        if (e.sender.dataSource.filter()) {
            e.sender.dataSource.filter().filters.forEach(function (f) {
                f.filters.forEach(function (g) {
                    if (g.field == "items" || g.field == "Subitems") {
                        $(e.container).css("width", "400px");
                        // this checks the corresponding checkbox in filter options menu
                        if (e.field == g.field) {
                            var checkbox = e.container.find("input[value='" + g.value + "']");
                            if (checkbox[0] != undefined && !checkbox[0].checked) {
                                e.container.find("input[value='" + g.value + "']").click()
                            }
                        }
                    }
                })                    
            })           
        }
    }

When I select a single filter, below is the value of e.sender.dataSource.filter():

{
    "filters": [
        {
            "value": "Slope",
            "operator": "contains",
            "field": "items"
        }
    ],
    "logic": "or"
}

Hence, e.sender.dataSource.filter().filters is

{
    "value": "Slope",
    "operator": "contains",
    "field": "items"
}

and below line throws error because I am iterating over e.sender.dataSource.filter().filters collection and expecting to see a filters property which is not the case:

f.filters.forEach(function (g) {

and I get a "Uncaught TypeError: Cannot read properties of undefined (reading 'forEach')"

 

Also, when I set two or more filters, here is the value of e.sender.dataSource.filter():

{
    "filters": [
        {
            "logic": "or",
            "filters": [
                {
                    "value": "Slope",
                    "operator": "contains",
                    "field": "items"
                }
            ]
        },
        {
            "value": "Span",
            "operator": "contains",
            "field": "Subitems"
        }
    ],
    "logic": "and"
}

Do you think I am not creating the correct JSON format for the grid filters? Do you expect to see a nested "filters" property in the first case when single column filter is selected?

For four filters, the value of e.sender.dataSource.filter():

{
    "filters": [
        {
            "logic": "or",
            "filters": [
                {
                    "value": "Slope",
                    "operator": "contains",
                    "field": "items"
                }
            ]
        },
        {
            "value": "Systems",
            "operator": "contains",
            "field": "Categories"
        },
        {
            "value": "Pipes",
            "operator": "contains",
            "field": "Categories1"
        },
        {
            "value": "Support",
            "operator": "contains",
            "field": "Categories2"
        }
    ],
    "logic": "and"
}

Stoyan
Telerik team
commented on 07 Feb 2023, 05:50 PM

Hi James,

Thank you for sharing such detailed information about the remaining issue.

I noticed that in your modified code the f.filters.forEach() is still present, as well as some additional conditional checks that shouldn't be needed with the modification I've suggested earlier.

If the Grid you use has an Items and Subitems field the suggested approach should work even if you copy the snippet I've shared.

In addition I've prepared a Telerik REPL sample that showcases the approach applied to a different Model.

Tags
Grid
Asked by
James
Top achievements
Rank 1
Answers by
Stoyan
Telerik team
Share this question
or