[OPEN] [#1578] [4.5.1] TreeGrid drag & drop is slow when store is filtered in Ext.Net 4.5.1

  1. #1

    [OPEN] [#1578] [4.5.1] TreeGrid drag & drop is slow when store is filtered in Ext.Net 4.5.1

    Hi Support,

    After upgrading Ext.Net from 4.1 to 4.5.1, I see drag & drop is slow when store is filtered. I'm attaching sample to reproduce the issue. Try drag & drop before filtering any records and then try after filtering, you'll notice delay, that wasn't the case in earlier version (4.1)

    Sample

    Controller:

      public class TestController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
    
    
            public StoreResult GetNodes(int node)
            {
                NodeCollection nodes = new Ext.Net.NodeCollection();
    
                if (node == 0)
                {
                    for (int i = 1; i < 50; i++)
                    {
                        Node asyncNode = new Node();
                        asyncNode.Text =   i.ToString();
                        asyncNode.NodeID = i.ToString();
                        asyncNode.CustomAttributes.Add(new ConfigItem("task", "Task_"  + i));
                        asyncNode.CustomAttributes.Add(new ConfigItem("user", "User_" + i));
                        asyncNode.CustomAttributes.Add(new ConfigItem("duration", "11.6"));
                        asyncNode.CustomAttributes.Add(new ConfigItem("done", "True"));
                        nodes.Add(asyncNode);
                    }
    
    
                }
                else
                {
                    for (int i = 0; i < 6; i++)
                    {
                        Node treeNode = new Node();
                        treeNode.Text = node + i.ToString();
                        treeNode.NodeID = node + i.ToString();
                        treeNode.Leaf = true;
                        treeNode.CustomAttributes.Add(new ConfigItem() { Name = "task", Value = "Sub_Task_" + node.ToString() + i });
                        treeNode.CustomAttributes.Add(new ConfigItem() { Name = "user", Value = "User_" + node + i });
                        treeNode.CustomAttributes.Add(new ConfigItem() { Name = "duration", Value = "11.6" });
                        treeNode.CustomAttributes.Add(new ConfigItem() { Name = "done", Value = "True" });
                        nodes.Add(treeNode);
                    }
                }
    
                return this.Store(nodes);
            }
       }
    View

    @using ScriptMode = Ext.Net.ScriptMode
    @{
        ViewBag.Title = "Index";
        Layout = null;
        var X = Html.X();
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <meta name="viewport" content="width=device-width"/>
        <title>TreeGrid</title>
        @Html.X().ResourceManager().ScriptMode(ScriptMode.Debug)
        <script>
            var formatHours = function(v) {
                if (v < 1) {
                    return Math.round(v * 60) + " mins";
                } else if (Math.floor(v) !== v) {
                    var min = v - Math.floor(v);
                    return Math.floor(v) + "h " + Math.round(min * 60) + "m";
                } else {
                    return v + " hour" + (v === 1 ? "" : "s");
                }
            };
    
            var handler = function(grid, rowIndex, colIndex, actionItem, event, record, row) {
                Ext.Msg.alert('Editing' + (record.get('done') ? ' completed task' : ''), record.get('task'));
            };
    
    
            var filterTreeGrid = function(filter) {
                var grid = App.tgProjects;
                grid.store.clearFilter();
                grid.store.filter({
                    filterFn: function(record) {
                        var isEven = (record.data.index % 2) == true;
                        return filter == 'even' ? isEven : !isEven;
                    }
                });
            }
    
            function nodeDragOver(targetNode, position, dragData) {
                 debugger;
                if (position == 'after' || position == 'before') {
                    var rec = dragData.records[0];
                    var sourceParentId = rec.data.parentId;
                    var targetParentId = targetNode.data.parentId;
                    // Move only root's child
                    if (sourceParentId == '0' && targetParentId == '0') {
                        return true;
                    }
                }
                return false;
            };
        </script>
    </head>
    <body>
    <div>
    
        <br/>
    
    
        @(
            X.TreePanel().ID("tgProjects")
                .Title("Core Team Projects")
                .Width(900)
                .Height(700)
                .RootVisible(false)
                .MultiSelect(true)
                .SingleExpand(true)
                .RowLines(true)
                .ColumnLines(true)
                .FolderSort(false)
                //.BufferedRenderer(false)
                .TopBarItem(
                    X.Button().Text("Odd rows").Handler("filterTreeGrid('odd');").Icon(Icon.Add),
                    X.Button().Text("Even rows").Handler("filterTreeGrid('even');").Icon(Icon.Add)
                )
                .Store(
                    Html.X().TreeStore().ID("treeGridStore")
                        .Proxy(
                            Html.X().AjaxProxy().Url(Url.Action("GetNodes"))
                        )
                        .Model(Html.X().Model()
                            .Fields(
                                X.ModelField().Name("task"),
                                X.ModelField().Name("user"),
                                X.ModelField().Name("duration"),
                                X.ModelField().Name("done").Type(ModelFieldType.Boolean)
                            )
                        )
                )
                .ColumnModel(
                    X.TreeColumn().Text("Task").Flex(1).DataIndex("task"),
                    X.TemplateColumn().Text("Duration").Flex(1).DataIndex("duration")
                        .Template(t =>
                        {
                            t.Html = "{duration:this.formatHours}";
                            t.Functions.Add(new JFunction
                            {
                                Name = "formatHours",
                                Fn = "formatHours"
                            });
                        }),
                    X.Column().Text("Assigned To").Flex(1).DataIndex("user").Editor(X.TextField().AllowBlank(false)),
                    X.Column().Text("Assigned BY").Flex(1).Editor(X.TextField().AllowBlank(true)),
                    X.CheckColumn().Text("Done").DataIndex("done").Flex(1).Editable(true).StopSelection(false)
                )
                .Root(
                    Html.X().Node().NodeID("0").Text("Root")
                )
                .Listeners(l => l.NodeDragOver.Fn = "nodeDragOver")
                .Plugins(
                    X.CellEditing().ClicksToEdit(1)
                )
                .View(
                    Html.X().TreeView()
                        .Plugins(
                            Html.X().TreeViewDragDrop().AllowLeafDrop(false).ContainerScroll(true)
                        //Html.X().TreeViewDragDrop().AppendOnly(true).ContainerScroll(true)
                        )
                )
              )
    </div>
    </body>
    </html>
    UPDATE:
    So I added check so you can only re-arrange rows at the root level, I also enabled debug mode for script but didn't get any errors. what kind of errors you got?

    Filter's logic we used is fairly simple, no complex calculation so can't optimize anymore. Is there any way we can disable the filter re-applying after every drop since i'm basically working on same filtered data? Is this new behavior is from underlying extjs or from Ext.Net?
    Last edited by Fahd; Mar 01, 2018 at 6:54 PM.
  2. #2
    Hello @Fahd! Thanks for the report and test case!

    I'm not really getting a slow behavior. I'm getting script errors. Have you tried to run your sample with the browser's developer tools enabled (and resource manager set up with .ScriptMode("Debug")?

    Probably you're running it in release scripts' mode and you are not even getting the error message.

    Now if I just disable buffered rendering (this is mostly useful for very big grids), then the drag and drop happens slightly slower, yes, but it is not that noticeable... well, maybe this is not very noticeable to me if I have a fast machine, and it may be noticeable on laptops, for example.

    To disable buffered rendering, you can just add the line .BufferedRenderer(false) to your TreeGrid definition.

    I see there's a change in the pace the drop works. Essentially, it is reapplying the filters every drop, as the very view is refreshed as you move the items around.

    I noticed also in your samples I can drop some entries "to oblivion", specially when I drop a node within another node. I didn't need to the bottom of it to realize that the record is just being "filtered out" as its index changes within the node it was dropped, so I drop one within a node, if it shows, the next one to be dropped will be filtered out.

    As solutions I may suggest either optimizing out the custom filter for speed or apply server-side filtering so that client side doesn't think the store is filtered and does not need to keep re-applying the filter every drop.

    As for why it worked before and now is slow, I can't say exactly what, but I'm confident the fact it didn't refresh the filter every drop caused problems. This re-applying of filters probably may be avoided in another way, if you think you really need it just not re-apply filter every drop, we can take a deeper look at it and see if there's another approach for the issue.

    Well, I hope this helps!
    Fabrício Murta
    Developer & Support Expert
  3. #3
    Hi Fabricio,

    Please see my update in last reply. Also I have fix which seems to help I'm not sure if it's the right one.

    I added Suspend/Resume store event and it improved the performance a lot.

    .Listeners(l => l.Drop.Handler = "App.tgProjects.getStore().resumeEvents(); App.tgProjects.getView().refresh();")
    .Listeners(l => l.BeforeDrop.Handler = "App.tgProjects.getStore().suspendEvents();")
    Please let me know what you think about it.

    Thanks
  4. #4
    Hello @Fahd!

    About your updated post:
    I also enabled debug mode for script but didn't get any errors. what kind of errors you got?
    The error returned by Ext.NET after the first drop with the filtered grid is "Unknown record passed to BufferedRenderer#scrollTo". If you still don't get the error chances are you have something setting up BufferedRenderer to false by default. Maybe you're defaulting to a theme that fits the whole content (once filtered), thus the buffered records are none.

    The second inquiry in your updated post:
    Is this new behavior is from underlying extjs or from Ext.Net?
    I can't say now with 100% confidence, but I don't remember drag-drop or store events specific fixes that could affect or incur in this issue -- from our side -- since Ext.NET 4.1. So odds are the issue came from ExtJS.

    This bit was replied by yourself in your workaround code:
    Is there any way we can disable the filter re-applying after every drop since i'm basically working on same filtered data?
    Although you didn't disable the filter re-application, you effectively reduced the amount of times the filter is applied to one per drop.

    And as for your new post:
    I added Suspend/Resume store event and it improved the performance a lot.
    Yes, impressive! With that I could see that the problem is that the filter is being repeatedly applied, not just once! It seems the farther in the list (bottom/end) the least the amount of calls the filter gets. Every "call" here means applying for every row so, having 50 rows and dropping something in the 10th row would (roughly) call the filter function 40 x 50 times (~2000 times)!

    So your solution seems to be the best at the moment -- you saved your performance hit without completely turning off the store events, but concentrating it to one run by drop, I can't think a better solution for that.

    I've looked over the plug in code and it does not seem the issue comes from the specific plug in for Tree Grids, but from drag drop itself (Ext.DD.DropZone) which is not as specialized as the plug in and probably needed the change that affects the TreeGrid in order to address issues with other store-bound (record dropping) events.

    We've logged this issue under #1578 in our github bug tracker and will update here as soon as we have a definitive fix for this. For now, I believe your work around is good enough -- if it does not bring other issues for you.
    Fabrício Murta
    Developer & Support Expert

Similar Threads

  1. [CLOSED] Drag and Drop from Combobox to TreeGrid
    By IMehl in forum 3.x Legacy Premium Help
    Replies: 1
    Last Post: Sep 17, 2015, 5:47 PM
  2. [CLOSED] [1.x] TreeGrid Drag Row Drop Row
    By MP in forum 1.x Legacy Premium Help
    Replies: 2
    Last Post: Nov 22, 2013, 2:02 PM
  3. [CLOSED] Drag Drop problem in TreeGrid
    By legaldiscovery in forum 1.x Legacy Premium Help
    Replies: 4
    Last Post: Aug 17, 2011, 9:22 AM
  4. TreeGrid Drag&Drop on Leaf-Node
    By martin.mosimann in forum Feature Requests
    Replies: 7
    Last Post: Mar 21, 2011, 6:47 PM
  5. Replies: 0
    Last Post: Sep 27, 2010, 12:59 PM

Tags for this Thread

Posting Permissions