[CLOSED] TreeStore rejectChanges not working

Page 1 of 2 12 LastLast
  1. #1

    [CLOSED] TreeStore rejectChanges not working

    Hi,

    This is a follow up from this thread about Ext.NET 2:
    http://forums.ext.net/showthread.php...coming-through

    In that previous thread, we found a way to implement rejectChanges and commitChanges methods on the TreeStore because Ext JS 4 didn't have such methods.

    Now, Ext JS 5 seems to, but, I am finding at least in my scenario it is not working.

    Here is a modified version of the code posted in the previous thread, removing the tree store override, etc. I've also removed the custom writer stuff that the original thread started off about as it is not relevant here:

    <%@ Page Language="C#" %>
    
    <!DOCTYPE html>
    <html>
        <head runat="server">
            <title>Title</title>
            
            <ext:ResourcePlaceHolder Mode="ScriptFiles" />
        </head>
        <body>
            <ext:ResourceManager runat="server" />
            
            <ext:TreePanel ID="TreePanel1" runat="server" Width="300" Height="200">
                <TopBar>
                    <ext:Toolbar runat="server">
                        <Items>
                            <ext:Button Text="Delete" Icon="Delete">
                                <Listeners>
                                    <Click Handler="
                                        var tree = #{TreePanel1},
                                            node = tree.getSelectionModel().getSelection()[0];
                                        
                                            if (!node) return;
                                            node.remove();
                                            tree.getStore().sync({
                                                failure: function(batch) {
                                                    tree.getStore().rejectChanges();
                                                },
                                                success: function() {
                                                    tree.getStore().commitChanges();
                                                }
                                            });
                                        " />
                                </Listeners>
                            </ext:Button>
                        </Items>
                    </ext:Toolbar>
                </TopBar>
                <Root>
                    <ext:Node Text="Tasks" Expanded="true">
                        <Children>
                            <ext:Node Icon="Folder" Text="Delete this 0" Leaf="true" />
                            <ext:Node Icon="Folder" Text="Delete this 1" Leaf="true" />
                            <ext:Node Icon="Folder" Text="Delete this 2" Leaf="true" />
                        </Children>
                    </ext:Node>
                </Root>
                <Store>
                    <ext:TreeStore runat="server" AutoSync="false" ShowWarningOnFailure="false">
                        <Proxy>
                            <ext:AjaxProxy Url="TreeLoader.ashx">
                                <API Destroy="Destroy.ashx" />
                                <Writer>
                                    <ext:JsonWriter RootProperty="data" Encode="true" />
                                </Writer>
                            </ext:AjaxProxy>
                        </Proxy>
                    </ext:TreeStore>
                </Store>
            </ext:TreePanel>
        </body>
    </html>
    And destroy.ashx:

        public class Destroy : IHttpHandler
        {
            public void ProcessRequest(HttpContext context)
            {
                throw new NotSupportedException();
            }
    
            public bool IsReusable
            {
                get { return false; }
            }
        }
    Steps:
    - Select a category node
    - Press the delete button

    Expect:
    - Server returns error (as expected)
    - Client side failure handler is invoked (as expected)
    - It calls tree.getStore().rejectChanges(); but the tree does not show the node that failed to delete again...
    - By comparison, the Ext.NET 2 version does show the node that failed to delete

    Any thoughts/suggestions?

    If you have access to Ext JS 5.1 beta, I am happy to wait to see if that fixes this?

    Thanks!
    Last edited by Daniil; Jan 20, 2015 at 6:06 PM. Reason: [CLOSED]
  2. #2
    I figured out a solution for this.

    Here is the code, followed by explanation:

    <%@ Page Language="C#" %>
    <%@ Register TagPrefix="cc1" Namespace="Ext.Net3.Tests.FromNuGet.Trees.CustomWriter" Assembly="Ext.Net3.Tests.FromNuGet" %>
    
    <!DOCTYPE html>
    <html>
        <head runat="server">
            <title>Title</title>
            
            <ext:ResourcePlaceHolder Mode="ScriptFiles" />
    
            <script>
                /**
                 * @author Johan Vandeplas
                 * Courtesy: http://www.sencha.com/forum/showthread.php?230409-rejectChanges-Ext.data.TreeStore
                 */
                Ext.define('My.overrides.data.NodeInterface', {
                    override: 'Ext.data.NodeInterface',
                    statics: {
                        getPrototypeBody: function () {
                            var me = this,
                                protBody = me.callParent(arguments),
                                removeFn = protBody.remove;
    
    
                            protBody.remove = function () {
                                var me = this;
                                me.removedFromNode = me.parentNode;
                                me.removedFromIdx = me.parentNode.indexOf(me);
                                removeFn.apply(this, arguments);
                            };
                            return protBody;
                        }
                    }
                });
    
                Ext.define('My.overrides.data.TreeStore', {
                    override: 'Ext.data.TreeStore',
    
                    /**
                     * brings the store to previous state (rejects all uncommitted changes)
                     */
                    rejectChanges: function () {
                        var me = this,
                            recs = me.getRejectRecords(),
                            len = recs.length,
                            i = 0,
                            rec;
    
                        for (; i < len; i++) {
                            rec = recs[i];
                            rec.reject();
                            if (rec.phantom) {
                                me.remove(rec);
                            }
                        }
    
                        // Restore removed records back to their original positions
                        recs = me.getRemovedRecords(); // <-- only change here it to replace the null records property with getRemovedRecords();
                        if (recs) {
                            len = recs.length;
                            for (i = len - 1; i >= 0; i--) {
                                rec = recs[i];
    
                                if (rec.removedFromNode) { // new change. Using if, to preserve existing functionality
                                    rec.removedFromNode.insertChild(rec.removedFromIdx, rec);
                                } else {
                                    me.insert(rec.removedFrom || 0, rec);
                                }
                                rec.reject();
                            }
    
                            // Since removals are cached in a simple array we can simply reset it here.
                            // Adds and updates are managed in the data MixedCollection and should already be current.
                            recs.length = 0;
                        }
                    }
                });
            </script>
        </head>
        <body>
            <ext:ResourceManager runat="server" />
            
            <ext:TreePanel ID="TreePanel1" runat="server" Width="300" Height="200">
                <TopBar>
                    <ext:Toolbar runat="server">
                        <Items>
                            <ext:Button Text="Delete" Icon="Delete">
                                <Listeners>
                                    <Click Handler="
                                        var tree = #{TreePanel1},
                                            node = tree.getSelectionModel().getSelection()[0];
                                        
                                            if (!node)
                                                return;
    
                                            node.remove();
    
                                            tree.getStore().sync({
                                                failure: function(batch) {
                                                    tree.getStore().rejectChanges();
                                                },
                                                success: function() {
                                                    tree.getStore().commitChanges();
                                                }
                                            });
                                        " />
                                </Listeners>
                            </ext:Button>
                        </Items>
                    </ext:Toolbar>
                </TopBar>
                <Root>
                    <ext:Node Text="Tasks" Expanded="true">
                        <Children>
                            <ext:Node Icon="Folder" Text="Delete this 0" Leaf="true" />
                            <ext:Node Icon="Folder" Text="Delete this 1" Leaf="true" />
                            <ext:Node Icon="Folder" Text="Delete this 2" Leaf="true" />
                        </Children>
                    </ext:Node>
                </Root>
                <Store>
                    <ext:TreeStore runat="server" AutoSync="false" ShowWarningOnFailure="false">
                        <Model>
                            <ext:Model runat="server">
                                <Fields>
                                    <ext:ModelField Name="task" />
                                    <ext:ModelField Name="user" />
                                </Fields>
                            </ext:Model>
                        </Model>
                        <Proxy>
                            <ext:AjaxProxy Url="TreeLoader.ashx">
                                <API Destroy="Destroy.ashx" />
                                <Writer>
                                    <ext:JsonWriter RootProperty="data" Encode="true"/>
                                </Writer>
                                <ExtraParams>
                                    <ext:Parameter Name="example" Value="1" Mode="Raw" />
                                </ExtraParams>
                            </ext:AjaxProxy>
                        </Proxy>
                    </ext:TreeStore>
                </Store>
            </ext:TreePanel>
        </body>
    </html>
    To test:
    - Just try to delete a node. It will try to call the Destroy.ashx defined in the API which doesn't exist, which is fine for this test
    - Because delete will fail, reject changes will be called and work.

    This is very similar to the previous post for Ext.NET 2, with the following changes:

    I override TreeStore as before, but the only method I need to change is rejectChanges.

    rejectChanges method has this change:

    Original:

                                    me.insert(rec.removedFrom || 0, rec);
    New code:

                                if (rec.removedFromNode) { // new change. Using if, to preserve existing functionality
                                    rec.removedFromNode.insertChild(rec.removedFromIdx, rec);
                                } else {
                                    me.insert(rec.removedFrom || 0, rec);
                                }
    Explanation:

    Keeping the NodeInterface override provide earlier it tracks the tree nods removed with additional properties, removedFromNode and removedfFromIdx. Using that we can re-insert the removed node under the right parent in the right place. Without this, the base store (typically used for grids etc) doesn't know about parents.

    Hope this helps people.

    I will post a similar response to the sencha.com thread if it helps people upgrade there, too.
  3. #3
    Just a note that while this works for Ext.NET 3, I don't think it will work for Ext.NET 3.1 (as it does not quite work in Ext JS 5.1.0)

    Here is a sencha fiddle test:
    https://fiddle.sencha.com/#fiddle/fi5

    The same code doesn't work if you change the framework to Ext JS 5.1.0. The parent node (root) gets removed at the same time the selected node is removed...! I will try to look into that at some point...
  4. #4
    The same code doesn't work if you change the framework to Ext JS 5.1.0.
    Anup, let's look into that in greater details after incorporating ExtJS 5.1.0. Does that sound OK for you?
  5. #5
    That's perfectly fine. Thanks.
  6. #6
    So. I've just started to have a cursory look at this.

    Using the ASPX or the Fiddle Example, what is different is what happens in the first line of reject changes:

    rejectChanges: function () {
      var me = this,
          recs = me.getRejectRecords(),
          len = recs.length,
          i = 0,
          rec;
    When stepping through getRejectRecords(), it returns the root node, because root node has phantom property set to true. This causes it to be returned as a possible rejected record from inside getRejectRecords based on the filter it is applying. And this is why it gets removed, when it should not.

    What I don't get is why it is set to true. According to the Sencha documentation for that property it is "True when the record does not yet exist in a server-side database. Any record which has a real database identity set as its idProperty is NOT a phantom -- it's real." (And default is false).

    I checked and the idProperty is set to "id", and in my production code I can also see that the idProperty is set to "id" and that the id property itself is set to the id of the record from the server.

    I have not had the time to look to see where this gets set yet.

    If you have any thoughts let me know (no rush on weekend though!). I will resume looking into this on Monday.
    Last edited by anup; Jan 17, 2015 at 8:52 PM.
  7. #7
    Hi Anup,

    Thank you for the investigation. It really helped me to narrow the problem down faster.

    ExtJS 5.0.1 - root.phantom is false
    ExtJS 5.1.0 - root.phantom is true

    I check the phantom just after the initial page load using your test case.

    The following line has appeared in ExtJS 5.1.0 in the Ext.data.TreeStore's updateRoot method.
    // Because of the application of an ID, this client-created root will not be phantom.
    // Ensure it is correctly flagged as a phantom.
    // AFTER being registered and joined, otherwise onNodeInsert will set the needsSync flag.
    newRoot.phantom = true;
    There is a couple of things I could ask.

    1. Why .updateRoot() is being called at the initial page load? There is no update, it is just an initial root setting. Though, maybe, it is some internal logic under the ExtJS hood.

    2. Anyways, it looks to be a defect to have an initial root setting be treated as a phantom, doesn't it? Though, I am not entirely sure about possible underlying reasons.

    As a workaround, I can suggest this listener for the TreeStore.
    <Listeners>
        <RootChange Handler="item.phantom = false;" Single="true" />
    </Listeners>
    By the way, searching some clarifications on the Sencha forums I've found this very fresh discussion that is related to our topic.
    http://www.sencha.com/forum/showthread.php?297140

    If you would like to clarify our doubts asking Evan in that thread (or, maybe, create a separate bug report), we don't mind:)

    Anyways, I hope the workaround helps.
  8. #8
    Thanks for the investigation.

    I raised a bug report at Sencha per your suggestion:
    http://www.sencha.com/forum/showthre...93#post1085193

    Thanks also for the workaround. I confirm that works in my various tests. Much appreciated!

    I've also updated the Sencha Fiddle I mentioned earlier (with a commented out workaround matching your workaround so they can see the problem first. If you uncomment the rootChange listener, it then works).

    I think you can close this for now. We will see what they say regarding the bug report, whether it was 5.0 that was wrong in their view or whether their change for 5.1.0 had this unforeseen consequence...
  9. #9
    Thanks a lot for reporting to Sencha. They opened a bug.

    Created our Issue to track it.
    https://github.com/extnet/Ext.NET/issues/644

    I am closing this thread for now. If any new information appears I will post a follow-up.
  10. #10
    This issue is marked as fixed in ExtJS 5.1.1.
    https://www.sencha.com/forum/showthread.php?297171

    So, the fix already came to SVN trunk with the recent ExtJS 5.1.1 upgrade we did. It goes to the upcoming Ext.NET 3.2.0 release.
Page 1 of 2 12 LastLast

Similar Threads

  1. [CLOSED] TreePanel & TreeStore setRootNode() not working anymore?
    By cleve in forum 2.x Legacy Premium Help
    Replies: 2
    Last Post: Jan 15, 2013, 3:16 PM
  2. Replies: 5
    Last Post: Dec 13, 2012, 8:27 AM
  3. Replies: 2
    Last Post: Jun 06, 2012, 8:27 PM
  4. [CLOSED] .rejectChanges not removing an added row
    By peter.campbell in forum 1.x Legacy Premium Help
    Replies: 1
    Last Post: May 27, 2011, 3:15 PM
  5. About Store.rejectChanges method
    By sunny_sh in forum 1.x Help
    Replies: 2
    Last Post: Aug 09, 2009, 11:43 PM

Posting Permissions