This project is read-only.

Enum filtering

Jan 16, 2014 at 10:52 AM
Has anyone successfully managed to get filtering on enum types to work?
Specifically using EF 6 and linq queries. Stuck in the compare string function in the source code right now....pretty sure it can be done with expressions(they are int entries in the database after all).
Jan 16, 2014 at 11:46 AM
Edited Jan 16, 2014 at 11:46 AM
Figured it out(in case anyone cares)
First add a custom filter class to sourcecode(Filtering/Types/EnumFilterType.cs in my case):
    internal sealed class EnumFilterType : FilterTypeBase
    {
        public override Type TargetType
        {
            get { return typeof(Enum); }
        }

        public override GridFilterType GetValidType(GridFilterType type)
        {
            switch (type)
            {
                case GridFilterType.Equals:
                    return type;
                default:
                    return GridFilterType.Equals;
            }
        }

        public override object GetTypedValue(string value)
        {
            return value;
        }

        public override Expression GetFilterExpression(Expression leftExpr, string value, GridFilterType filterType)
        {
            //Custom implementation of string filter type. Case insensitive compartion.

            filterType = GetValidType(filterType);
            object typedValue = GetTypedValue(value);
            if (typedValue == null)
                return null; //incorrent filter value;

            Expression valueExpr = Expression.Constant(typedValue);
            Expression binaryExpression;
            switch (filterType)
            {
                case GridFilterType.Equals:
                    binaryExpression = GetEqualsСompartion(string.Empty, leftExpr, valueExpr);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            return binaryExpression;
        }
        private Expression GetEqualsСompartion(string methodName, Expression leftExpr, Expression rightExpr)
        {

            Type targetType = leftExpr.Type;

            var someValue = Expression.Constant(Enum.Parse(targetType,(rightExpr as System.Linq.Expressions.ConstantExpression).Value.ToString()));//Enum.ToObject(targetType, (rightExpr as System.Linq.Expressions.ConstantExpression).Value));

            var equalsExp = Expression.Equal(leftExpr, someValue);
            return equalsExp;
        }
    }
then inside FilterTypeResolver.cs change the GetFilterType function:
        public IFilterType GetFilterType(Type type)
        {
            foreach (IFilterType filterType in _filterCollection)
            {
                if (filterType.TargetType.FullName == type.FullName)
                    return filterType;
            }
            if (type.IsEnum)
            {
                return new EnumFilterType(); //try to process column type as text (not safe)
            }
            return new TextFilterType(); //try to process column type as text (not safe)
        }
That's it. for the clientside widget use the dropdown sample from the documentation...just fill it up with EnumValues(String value). It works automatically :)
function ProcessResultFilterWidget() {
    /***
    * This method must return type of registered widget type in 'SetFilterWidgetType' method
    */
    this.getAssociatedTypes = function () {
        return ["ProcessResultFilterWidget"];
    };
    /***
    * This method invokes when filter widget was shown on the page
    */
    this.onShow = function () {
        /* Place your on show logic here */
    };

    this.showClearFilterButton = function () {
        return true;
    };
    /***
    * This method will invoke when user was clicked on filter button.
    * container - html element, which must contain widget layout;
    * lang - current language settings;
    * typeName - current column type (if widget assign to multipile types, see: getAssociatedTypes);
    * values - current filter values. Array of objects [{filterValue: '', filterType:'1'}];
    * cb - callback function that must invoked when user want to filter this column. Widget must pass filter type and filter value.
    * data - widget data passed from the server
    */
    this.onRender = function (container, lang, typeName, values, cb, data) {
        //store parameters:
        this.cb = cb;
        this.container = container;
        this.lang = lang;

        //this filterwidget demo supports only 1 filter value for column column
        this.value = values.length > 0 ? values[0] : { filterType: 1, filterValue: "" };

        this.renderWidget(); //onRender filter widget
        this.setupDropDownValues(); //load customer's list from the server
        this.registerEvents(); //handle events
    };
    this.renderWidget = function () {
        var html = '<select style="width:250px;" class="grid-filter-type ProcessResultFilterWidgetList form-control">\
                    </select>';
        this.container.append(html);
    };
    this.setupDropDownValues = function () {
        var $this = this;

        var dropdownList = this.container.find(".ProcessResultFilterWidgetList");
        dropdownList.append('<option ' + ("Ready" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Ready">Ready</option>');
        dropdownList.append('<option ' + ("Processing" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Processing">Processing</option>');
        dropdownList.append('<option ' + ("Cancelled" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Cancelled">Cancelled</option>');
        dropdownList.append('<option ' + ("Failed" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Failed">Failed</option>');
        dropdownList.append('<option ' + ("Succeeded" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Succeeded">Succeeded</option>');
        dropdownList.append('<option ' + ("Unknown" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Unknown">Unknown</option>');
        dropdownList.append('<option ' + ("Timeout" == this.value.filterValue ? 'selected="selected"' : '') + ' value="Timeout">Timeout</option>');


    };

    /***
    * Internal method that register event handlers for 'apply' button.
    */
    this.registerEvents = function () {
        //get list with customers
        var dropdownList = this.container.find(".ProcessResultFilterWidgetList");
        //save current context:
        var $context = this;
        //register onclick event handler
        dropdownList.change(function () {
            //invoke callback with selected filter values:
            var values = [{ filterValue: $(this).val(), filterType: 1 /* Equals */ }];
            $context.cb(values);
        });
    };

}
Jan 17, 2014 at 3:48 AM
Hi,

Thanks. I'll inspect your code and add it to the next release
Jan 18, 2014 at 10:49 AM
This idea was useful now that EF has good support for enums.

I'm using this method added to GridExtensions.cs to generate the javascript filter widget function automatically from the enum type:
    public static IHtmlString RenderGridEnumFilter(this HtmlHelper helper, string gridName, string filterWidgetName, Type enumType) {
        var opts = new StringBuilder();
        foreach (var v in Enum.GetNames(enumType)) {
            opts.AppendFormat(@"sel.append('<option ' + (""{0}"" == v ? 'selected=""selected""' : '') + ' value=""{0}"">{0}</option>');", v);
        }
        var script = string.Format(@"
<script type=""text/javascript"">
function {0}() {{
    this.getAssociatedTypes = function () {{ return ['{0}']; }};
    this.onShow = function () {{ }};
    this.showClearFilterButton = function () {{ return true; }};
    this.onRender = function (container, lang, typeName, values, cb, data) {{
        var _this = this;
        var _cb = cb;
        container.append('<select class=""grid-filter-type {0}List form-control""></select>');
        var sel = container.find('.{0}List');
        var v = (values.length > 0 ? values[0] : {{ filterType: 1, filterValue: '' }}).filterValue;
        {1}
        sel.change(function () {{ _cb([{{ filterValue: $(this).val(), filterType: 1 }}]); }});
    }};
}}
$(function () {{
    pageGrids.{2}.addFilterWidget(new {0}());
}});
</script>
        ", filterWidgetName, opts, gridName);
        return helper.Raw(script);
    }
}
Usage in a .cshtml file is like this:
@section scripts {
    @Html.RenderGridEnumFilter("LoggedErrorGrid", "FaultSeverityFilterWidget", typeof(FaultSeverity))
}
Jan 18, 2014 at 10:51 AM
Of course it would be better if the Grid was smart enough to emit this function automatically just from knowing the type of an added column :-)
Jan 18, 2014 at 1:06 PM
Awesome job. Changed it for my purposes but saved me hours of writing javascript :)
Jun 30, 2014 at 2:39 PM
Oustanding job, TonesNotes. Definitely needs to be added to the project.

Confirmed working here. Agreed that this saves hours.
Jul 28, 2014 at 4:03 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Aug 7, 2014 at 1:34 AM
Edited Aug 7, 2014 at 2:03 AM
Great discussion here... one thing to notice is that the code provided by Looooooka needs to be added to the repo so that we can use TonesNotes HTML helper.

I also confirm it works as expected.

Bukharin: if you want I can submit a patch with all the changes. I just didn't add any tests.

I created a repo in GitHub with the changes if anyone is interested: https://github.com/leniel/Grid.Mvc

CodePlex doesn't allow forks for gridmvc. At least not for its type of source control... :(


By the way: the project source code is well organized. Great job!


Thank you very much guys. The sum of forces build a great software product. :D