[CLOSED] What's a good way to "loadbalance" our gridfilter?

  1. #1

    [CLOSED] What's a good way to "loadbalance" our gridfilter?

    As data in our grids grow/shrink in size it would be really nice to adjust our gridfilter to switch from local client-side to remote server-side functionality.

    So imagine the data grows beyond a certain threshold, to where it becomes unreasonable and inefficient to filter it in the client browser. We would want to have this performed by the server side logic.
    On the other hand for situations with a reasonable dataset size, we would be better off doing this on the client side.

    Because we may need custom logic built into our filtering operation, we would also want to have mechanisms in the filter and/or store configurations to let us hook them into our client-side and server-side custom filtering code. Thanks for any suggestions!
    Last edited by fabricio.murta; May 12, 2017 at 9:57 PM. Reason: no feedback from the user in 7+ days
  2. #2
    Hello Caleb!

    Based on the sample test case you provided on your previous thread, and also the GridFilters Remote sample, we've extended it to this:

    <%@ Page Language="C#" %>
    
    <!DOCTYPE html>
    
    <script runat="server">
        // Just a variable to "emulate" the many or few data, being true, should use server-side filtering.
        public static bool bigdata = true;
    
        // Define model classes to make code more readable
        public class MapItem
        {
            public int IDVal { get; set; }
            public string DisplayVal { get; set; }
        }
    
        public class DataItem
        {
            public string businessVal { get; set; }
            public int FK_ID { get; set; }
        }
    
        // The same data should be accessed from different places (e.g. data available on database)
        public static List<MapItem> MapData = new List<MapItem>
        {
            new MapItem {IDVal = 0, DisplayVal = "Some"},
            new MapItem {IDVal = 1, DisplayVal = "Something"},
            new MapItem {IDVal = 2, DisplayVal = "Somehow"},
            new MapItem {IDVal = 3, DisplayVal = "Someway"},
            new MapItem {IDVal = 4, DisplayVal = "Someone"}
        };
    
        public static List<DataItem> Data = new List<DataItem>
        {
            new DataItem {businessVal = "Record A: Some", FK_ID = 0},
            new DataItem {businessVal = "Record B: Something", FK_ID = 1},
            new DataItem {businessVal = "Record C: Some", FK_ID = 0},
            new DataItem {businessVal = "Record D: Something", FK_ID = 1},
            new DataItem {businessVal = "Record E: Somehow", FK_ID = 2},
            new DataItem {businessVal = "Record F: Some", FK_ID = 0}
        };
    
        protected void Page_Load(object sender, EventArgs e)
        {
            MapStore.DataSource = MapData;
            MapStore.DataBind();
    
            if (bigdata)
            {
                GridStore.RemoteFilter = true;
                MaskedColumn.Filter.Add(new StringFilter());
            }
            else
            {
                // make the grid use local filtering
                // GridStore.RemoteFilter = false; // this is the setting by default, no need to force
                MaskedColumn.Filter.Add(new StringFilter()
                {
                    CustomConfig = { new ConfigItem() { Name = "filterFn", Value = "filterFn_DisplayVal", Mode = ParameterMode.Raw } }
                });
            }
        }
    
        // Get the entry given its ID.
        private string getMapDataByID(int id)
        {
            return MapData.First(entry => entry.IDVal == id).DisplayVal;
        }
    
        // Extracted from https://examples4.ext.net/#/GridPanel/Plugins/GridFilters_Remote/
        protected void GridStore_RefreshData(object sender, StoreReadDataEventArgs e)
        {
            var gridData = new List<DataItem>(); // benefit from the List class facilities
    
            // create a copy of the data (or changes in the list will affect the
            // static variable)
            gridData.AddRange(Data);
    
            // The filter will be empty on first load (and no following queries
            // will be made if sorting is local).
            string s = e.Parameters["filter"];
    
            if (!string.IsNullOrEmpty(s))
            {
                FilterConditions fc = new FilterConditions(s);
    
                foreach (FilterCondition condition in fc.Conditions)
                {
                    Comparison comparison = condition.Comparison;
                    string field = condition.Field;
                    FilterType type = condition.Type;
    
                    object value;
                    switch (condition.Type)
                    {
                        case FilterType.Boolean:
                            value = condition.Value<bool>();
                            break;
                        case FilterType.Date:
                            value = condition.Value<DateTime>();
                            break;
                        case FilterType.List:
                            value = condition.List;
                            break;
                        case FilterType.Number:
                            if (gridData.Count > 0 && gridData[0].GetType().GetProperty(field).PropertyType == typeof(int))
                            {
                                value = condition.Value<int>();
                            }
                            else
                            {
                                value = condition.Value<double>();
                            }
    
                            break;
                        case FilterType.String:
                            value = condition.Value<string>();
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
    
                    gridData.RemoveAll(
                        item =>
                        {
                            object oValue = item.GetType().GetProperty(field).GetValue(item, null);
    
                            // If the field filtered is the one we want the special
                            // behavior then just replace the value to check
                            // against to its string mapped value
                            if (field == "FK_ID")
                            {
                                // No reflection needed, we know we want FK_ID in this case.
                                oValue = getMapDataByID(item.FK_ID);
                            }
    
                            IComparable cItem = oValue as IComparable;
    
                            switch (comparison)
                            {
                                case Comparison.Eq:
                                    if (type == FilterType.List)
                                    {
                                        return !(value as List<string>).Contains(oValue.ToString());
                                    }
                                    return !cItem.Equals(value);
                                case Comparison.Like:
                                    switch (type)
                                    {
                                        case FilterType.List:
                                            return !(value as List<string>).Any(val => oValue.ToString().Contains(val));
                                        case FilterType.String:
                                            return !oValue.ToString().Contains(value.ToString());
                                        default:
                                            return !cItem.Equals(value);
                                    }
                                case Comparison.In:
                                    switch (type)
                                    {
                                        case FilterType.List:
                                            return !(value as List<string>).Contains(oValue.ToString());
                                        case FilterType.String:
                                            return !oValue.ToString().StartsWith(value.ToString());
                                        default:
                                            return !cItem.Equals(value);
                                    }
    
                                case Comparison.Gt:
                                    return cItem.CompareTo(value) < 1;
                                case Comparison.Lt:
                                    return cItem.CompareTo(value) > -1;
                                default:
                                    throw new ArgumentOutOfRangeException();
                            }
                        }
                    );
                }
            }
    
            GridStore.DataSource = gridData;
            GridStore.DataBind();
        }
    
        protected void Toggle_BigData(object sender, DirectEventArgs e)
        {
            bigdata = !bigdata;
            X.Reload();
        }
    </script>
    
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>61872 - Custom header dropdown filter (user provided)</title>
        <script type="text/javascript">
            var lookupDisplayVal = function (IDValue) {
                var mappingEntry = App.MapStore.getById(IDValue);
    
                if (Ext.isEmpty(mappingEntry)) {
                    return IDValue;
                }
                return mappingEntry.data.DisplayVal;
            };
    
            var filterFn_DisplayVal = function (gridrow, filterVal) {
                //in a non-hardcoded situation with undetermined number of columns, is there a good way for this filterFn handler to identify which gridview column links to the filterVal?
                var lookupVal = lookupDisplayVal(gridrow.data.FK_ID);
    
                if (Ext.isEmpty(lookupVal)) {
                    return false;
                }
    
                return (lookupVal.toLowerCase().indexOf(filterVal.toLowerCase()) > -1);
            };
        </script>
    </head>
    <body>
        <form runat="server">
            <ext:ResourceManager runat="server" />
            <ext:Store ID="MapStore" runat="server">
                <Model>
                <ext:Model runat="server" IDProperty="IDVal">
                    <Fields>
                        <ext:ModelField Name="IDVal" Type="Int" />
                        <ext:ModelField Name="DisplayVal" Type="String" />
                    </Fields>
                </ext:Model>
                </Model>
            </ext:Store>
            <ext:GridPanel Title="Grid Panel" ID="GridPanel1" runat="server" Width="650" Height="325">
                <TopBar>
                    <ext:Toolbar runat="server">
                        <Items>
                            <ext:Button runat="server" Text="Toggle big data" OnDirectClick="Toggle_BigData" />
                            <ext:Label runat="server">
                                <Content>
                                    Big data approach (remote filtering) is: <b><%: bigdata %></b>
                                </Content>
                            </ext:Label>
                        </Items>
                    </ext:Toolbar>
                </TopBar>
                <Store>
                    <ext:Store ID="GridStore" runat="server" OnReadData="GridStore_RefreshData">
                        <%-- This makes the server-side requests to load data (including first load).
                             Makes server side request for filters if RemoteFilter is true. --%>
                        <Proxy>
                            <ext:PageProxy />
                        </Proxy>
                        <Model>
                            <ext:Model runat="server">
                                <Fields>
                                    <ext:ModelField Name="businessVal" Type="String" />
                                    <ext:ModelField Name="FK_ID" Type="Int" />
                                </Fields>
                            </ext:Model>
                        </Model>
                    </ext:Store>
                </Store>
                <ColumnModel runat="server">
                    <Columns>
                        <ext:Column runat="server" Text="ExampleVals" DataIndex="businessVal" Flex="1">
                            <Filter>
                                <ext:StringFilter />
                            </Filter>
                        </ext:Column>
                        <ext:Column ID="MaskedColumn" runat="server" Text="Masked FK Editor" DataIndex="FK_ID" Flex="2">
                            <Renderer Fn="lookupDisplayVal" />
                            <Editor>
                                <ext:ComboBox 
                                    runat="server"
                                    QueryMode="Local"
                                    Editable="true"
                                    StoreID="MapStore"
                                    DisplayField="DisplayVal"
                                    ValueField="IDVal"
                                    />
                            </Editor>
                        </ext:Column>
                    </Columns>
                </ColumnModel>
                <Plugins>
                    <ext:CellEditing runat="server" />
                    <ext:GridFilters runat="server" />                
                </Plugins>
            </ext:GridPanel>        
        </form>
    </body>
    </html>
    Some considerations:
    - I don't see much sense on switching the grid from local to remote without a full page reload (as the data in the database will be brought in during the page load) -- so the button to switch modes (to emulate either a big or a small database size) also reloads the page. Changes a static variable which is reset each time the server thread (IIS/IISExpress) is started/restarted.
    - If you want to switch to "local filtering" once the filter reduces greatly the amount of entries, what if the user clears or chooses a filter which matches more entries? Any filter pattern in potential can bring all data from the server
    - The server-side filtering logic should be tailored for your data, it is not a generic code that should work for all cases and is provided just to illustrate the functionality and some possibilities of extension (preserved from the original example)
    - To "see" the server queries, open browser's developer tools and go to the network monitor tab. Clear the requests and change the filter to see the requests being made. This won't happen when the "bigdata" variable is toggled to false with the Toggle big data button.

    Well, I hope this helps!
    Fabrício Murta
    Developer & Support Expert
  3. #3
    Hey Fabricio, thanks so much for drafting a design for this. Will think about this and your considerations when I get a chance!
  4. #4
    Alright! Hope it will be of help!

    So, if you don't need more assistance with this thread, are we alright to mark it as closed?

    Even if we mark it as closed, you will be able to post follow-ups in the future if you feel like.
    Fabrício Murta
    Developer & Support Expert

Similar Threads

  1. Replies: 1
    Last Post: Apr 06, 2016, 12:05 PM
  2. Replies: 6
    Last Post: May 31, 2013, 3:04 AM
  3. Replies: 5
    Last Post: May 02, 2012, 5:37 PM
  4. Replies: 4
    Last Post: Oct 11, 2011, 2:42 AM
  5. Replies: 3
    Last Post: May 25, 2011, 9:50 AM

Tags for this Thread

Posting Permissions