Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Tree.remove () does not remove children

    The docs for tree.remove () say:
    Code:
    Removes a node, along with all its children.
    However, when I call tree.remove (), the treeNode is deleted, but it seems to me that its children are not.
    Note that the treeNode and its children are removed from the UI, but not from the Tree memory.
    I have to explicitly delete all children records recursively for them to be actually deleted.

    Versions
    GWT 2.3.0
    SmartGWT 3.0p-2012-04-26
    SmartGWT 3.0p-2012-08-31 (latest)
    Dev console: SmartClient Version: v8.2p_2012-08-31/LGPL Development Only (built 2012-08-31)
    FireFox 6.0

    Steps to reproduce
    1. Create and configure a TreeGrid + Tree
    2. Load the tree with at least 1 root and 1 child
    3. Call tree.remove () to remove the root
    4. Load the tree with the same root and child as #2, and you get this exception:
    Code:
    00:00:56.388 [ERROR] 13:11:10.404:MUP6:WARN:Tree:isc_Tree_0:Adding node to tree with id property set to:3. A node with this ID is already present in this Tree - th
     at node will be replaced. Note th
     at this warning may be disabled by setting the reportCollisions attribute to false.com.smartgwt.client.core.JsObject$SGWT_WARN: 13:11:10.404:MUP6:WARN:Tree:isc_Tree_0:Adding node to tree with id property set to:3. A node with this ID is already present in this Tree - th
     at node will be replaced. Note th
     at this warning may be disabled by setting the reportCollisions attribute to false.
     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
     at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
     at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:105)
     at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
     at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:167)
     at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessagesWhileWaitingForReturn(BrowserChannelServer.java:326)
     at com.google.gwt.dev.shell.BrowserChannelServer.invokeJavascript(BrowserChannelServer.java:207)
     at com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:132)
     at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:561)
     at com.google.gwt.dev.shell.ModuleSpace.invokeNativeObject(ModuleSpace.java:269)
     at com.google.gwt.dev.shell.JavaScriptHost.invokeNativeObject(JavaScriptHost.java:91)
     at com.smartgwt.client.widgets.tree.Tree.add(Tree.java)
     at TestTreeRemove$1.onClick(TestTreeRemove.java:77)
    And this in the dev console:
    Code:
    13:20:13.809:INFO:Log:initialized
    13:20:13.819:WARN:Log:NOTE: Firebug is enabled. Firebug greatly slows the performance of applications that make heavy use of JavaScript. Isomorphic highly recommends Firebug for troubleshooting, but Firebug and other development tools should be disabled when assessing the real-world performance of SmartClient applications.
    13:20:14.457:WARN:Log:New Class ID: 'EditPane' collides with ID of existing Class object '[DataSource ID:EditPane]'.  Existing object will be replaced.
    This conflict would be avoided by disabling ISC Simple Names mode.  See documentation for further information.
    13:20:19.570:INFO:Log:isc.Page is loaded
    13:20:32.749:MUP7:WARN:Tree:isc_Tree_0:Adding node to tree with id property set to:3. A node with this ID is already present in this Tree - that node will be replaced. Note that this warning may be disabled by setting the reportCollisions attribute to false.
    13:20:32.752:MUP7:WARN:Tree:isc_Tree_0:Adding node to tree with id property set to:4. A node with this ID is already present in this Tree - that node will be replaced. Note that this warning may be disabled by setting the reportCollisions attribute to false.
    With the code sample below, you can reproduce the not-working case by clicking:
    1. Load
    2. Dump Tree, to verify that the parent/child have been added
    3. Remove Root A, to let tree.remove (TreeNode) remove a node
    4. Dump Tree, to verify that the tree claims that the records are deleted
    5. Load, to get the failure above

    If I replace tree.remove () with tree.removeTreeNodeExceptForRootTreeNode (), then it seems to work:
    Code:
      /**
       * Removes the given TreeNode and all its children
       * @param tree
       * @param treeNode
       */
      private void removeTreeNodeExceptForRootTreeNode (
        final Tree tree,
        final TreeNode treeNode) {
        
        // remove children first
        final TreeNode[] childTreeNodeArray = tree.getChildren (treeNode);
        if (childTreeNodeArray != null) {
          for (final TreeNode childTreeNode : childTreeNodeArray) {
            removeTreeNodeExceptForRootTreeNode (tree, childTreeNode);
          }
        }
        
        // finally remove this node, unless it is the root
        if (treeNode.getAttributeAsInt (tree.getIdField ()) != 1) {
          tree.remove (treeNode);
        }
        else {
          GWT.log ("removeTreeNodeRecursive (): not deleting rootTreeNode");
        }
      }
    With the code sample below, you can reproduce the working case by clicking:
    1. Load
    2. Dump Tree, to verify that the parent/child have been added
    3. Remove Root A Explicit, to delete all children manually
    4. Dump Tree, to verify that the tree claims that the records are deleted
    5. Load, to verify that the tree seems properly built again, and without errors
    Code:
    13:18:23.081:INFO:Log:initialized
    13:18:23.090:WARN:Log:NOTE: Firebug is enabled. Firebug greatly slows the performance of applications that make heavy use of JavaScript. Isomorphic highly recommends Firebug for troubleshooting, but Firebug and other development tools should be disabled when assessing the real-world performance of SmartClient applications.
    13:18:23.738:WARN:Log:New Class ID: 'EditPane' collides with ID of existing Class object '[DataSource ID:EditPane]'.  Existing object will be replaced.
    This conflict would be avoided by disabling ISC Simple Names mode.  See documentation for further information.
    13:18:28.408:INFO:Log:isc.Page is loaded
    Code to reproduce:
    Code:
    import com.smartgwt.client.widgets.tree.TreeGrid;
    import com.smartgwt.client.widgets.tree.Tree;
    import com.smartgwt.client.widgets.tree.TreeGridField;
    import com.smartgwt.client.widgets.tree.TreeNode;
    import com.smartgwt.client.widgets.layout.VLayout;
    import com.smartgwt.client.widgets.events.ClickHandler;
    import com.smartgwt.client.widgets.events.ClickEvent;
    import com.smartgwt.client.widgets.IButton;
    import com.smartgwt.client.types.SortDirection;
    import com.smartgwt.client.types.TreeModelType;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.core.client.GWT;
    
    public class TestTreeRemove implements EntryPoint {
      
    
      /**
       * The EntryPoint interface
       */
      public void onModuleLoad () {
    
        // configure Tree
        final Tree tree = new Tree ();
        tree.setModelType (TreeModelType.PARENT);
        tree.setNameProperty ("name");
        tree.setIdField ("nodeId");
        tree.setParentIdField ("parentNodeId");
        tree.setShowRoot (true);
    
        // configure root TreeNode
        final TreeNode rootTreeNode = new TreeNode ("root");
        rootTreeNode.setAttribute (tree.getIdField (), 1);
        
        tree.setRoot (rootTreeNode);
    
        // configure TreeGrid
        final TreeGridField nameField = new TreeGridField ("name", "Name", 200);
        
        final TreeGrid treeGrid = new TreeGrid ();
        treeGrid.setWidth (300);
        treeGrid.setHeight (200);
        treeGrid.setSortFoldersBeforeLeaves (true);
        treeGrid.setSortDirection (SortDirection.ASCENDING);
        treeGrid.setFields (nameField);
        treeGrid.setData (tree);
        
        // Root A
        //   Root A - Child A
        //   Root A - Child B
        final TreeNode rootATreeNode = new TreeNode ();
        final IButton loadButton = new IButton ();
        loadButton.setTitle ("Load");
        loadButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            rootATreeNode.setAttribute (tree.getIdField (), 2);
            rootATreeNode.setAttribute (tree.getParentIdField (), 1);
            rootATreeNode.setAttribute ("name", "Root A");
            tree.add (rootATreeNode, rootTreeNode);
      
            final TreeNode childA = new TreeNode ();
            childA.setAttribute (tree.getIdField (), 3);
            childA.setAttribute (tree.getParentIdField (), 2);
            childA.setAttribute ("name", "Root A - Child A");
            tree.add (childA, rootATreeNode);
      
            final TreeNode childB = new TreeNode ();
            childB.setAttribute (tree.getIdField (), 4);
            childB.setAttribute (tree.getParentIdField (), 2);
            childB.setAttribute ("name", "Root A - Child B");
            tree.add (childB, rootATreeNode);
          }
        });
    
        // button to delete the root
        final IButton removeRootAButton = new IButton ();
        removeRootAButton.setTitle ("Remove Root A");
        removeRootAButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            tree.remove (rootATreeNode);
          }
        });
        
        // button to delete the root
        final IButton removeRootARecursiveButton = new IButton ();
        removeRootARecursiveButton.setTitle ("Remove Root A Explicit");
        removeRootARecursiveButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            removeTreeNodeExceptForRootTreeNode (tree, rootTreeNode);
          }
        });
    
        // button to dump the tree
        final IButton dumpTreeButton = new IButton ();
        dumpTreeButton.setTitle ("Dump Tree");
        dumpTreeButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            final TreeNode treeNode = tree.getRoot ();
            final String prefix = " ";
            dumpTreeNode (tree, treeNode, prefix);
          }
        });
    
        // layout
        final VLayout layout = new VLayout ();
        layout.addMember (treeGrid);
        layout.addMember (loadButton);
        layout.addMember (removeRootAButton);
        layout.addMember (removeRootARecursiveButton);
        layout.addMember (dumpTreeButton);
        layout.show ();
      }
    
      /**
       * Dumps the given TreeNode
       * @param tree
       * @param treeNode
       * @param prefix
       */
      private void dumpTreeNode (
        final Tree tree,
        final TreeNode treeNode,
        final String prefix) {
    
        // log
        GWT.log (
          prefix +
          "id: " + treeNode.getAttributeAsInt (tree.getIdField ()) +
          "parentId: " + treeNode.getAttributeAsInt (tree.getParentIdField ()) +
          "");
        
        // dump children
        final TreeNode[] childTreeNodeArray = tree.getChildren (treeNode);
        if (childTreeNodeArray != null) {
          for (final TreeNode childTreeNode : childTreeNodeArray) {
            dumpTreeNode (tree, childTreeNode, prefix + prefix);
          }
        }
      }
      
      /**
       * Removes the given TreeNode and all its children
       * @param tree
       * @param treeNode
       */
      private void removeTreeNodeExceptForRootTreeNode (
        final Tree tree,
        final TreeNode treeNode) {
        
        // remove children first
        final TreeNode[] childTreeNodeArray = tree.getChildren (treeNode);
        if (childTreeNodeArray != null) {
          for (final TreeNode childTreeNode : childTreeNodeArray) {
            removeTreeNodeExceptForRootTreeNode (tree, childTreeNode);
          }
        }
        
        // finally remove this node, unless it is the root
        if (treeNode.getAttributeAsInt (tree.getIdField ()) != 1) {
          tree.remove (treeNode);
        }
        else {
          GWT.log ("removeTreeNodeRecursive (): not deleting rootTreeNode");
        }
      }
    }
    Any advice appreciated...

    #2
    New test cases, same results...

    Here is a new test case: it is like the previous one, but the Load button only loads Root A.
    When you expand Root A, then it loads its children.

    1. Click Load Root A, which loads ONLY Root A
    2. Expand Root A, which loads the children because childArray is empty
    3. Click Remove Root A
    4. Click Load Root A, which loads ONLY Root A
    The tree shows Root A, and ALSO its children, confirming that tree.remove () is not working.

    And here is a similar test case:
    1. Click Load Root A, which loads ONLY Root A
    2. Expand Root A, which loads the children. onFolderOpenend (), childArray is empty
    3. Close Root A
    4. Click Remove Root A
    5. Click Load Root A, which loads ONLY Root A
    6. Expand Root A, which does NOT load children because childArray is of size 2
    This confirms that tree.remove () is not removing its children.

    The final and possibly most interesting issue here is this test case:
    1. Click Load Root A, which loads ONLY Root A, and Root A shows the icon to allow expanding it
    2. Expand Root A, which loads the children. onFolderOpenend (), childArray is empty
    3. Click Remove Root A Recursive (this is the method that deletes all children recursively)
    4. Click Load Root A, which loads ONLY Root A, except that this time it does NOT show the icon to allow expanding it

    If anybody has any ideas, shoot and I will gladly try them.


    Code:
    import com.smartgwt.client.widgets.tree.TreeGrid;
    import com.smartgwt.client.widgets.tree.Tree;
    import com.smartgwt.client.widgets.tree.TreeGridField;
    import com.smartgwt.client.widgets.tree.TreeNode;
    import com.smartgwt.client.widgets.tree.events.FolderOpenedHandler;
    import com.smartgwt.client.widgets.tree.events.FolderOpenedEvent;
    import com.smartgwt.client.widgets.layout.VLayout;
    import com.smartgwt.client.widgets.events.ClickHandler;
    import com.smartgwt.client.widgets.events.ClickEvent;
    import com.smartgwt.client.widgets.IButton;
    import com.smartgwt.client.types.SortDirection;
    import com.smartgwt.client.types.TreeModelType;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.core.client.GWT;
    
    public class TestTreeRemove implements EntryPoint {
      
    
      /**
       * The EntryPoint interface
       * See http://forums.smartclient.com/showthread.php?t=23429
       */
      public void onModuleLoad () {
    
        // configure Tree
        final Tree tree = new Tree ();
        tree.setModelType (TreeModelType.PARENT);
        tree.setNameProperty ("name");
        tree.setIdField ("nodeId");
        tree.setParentIdField ("parentNodeId");
        tree.setShowRoot (true);
    
        // configure root TreeNode
        final TreeNode rootTreeNode = new TreeNode ("root");
        rootTreeNode.setAttribute (tree.getIdField (), 1);
        
        tree.setRoot (rootTreeNode);
    
        // configure TreeGrid
        final TreeGridField nameField = new TreeGridField ("name", "Name", 200);
        final TreeGrid treeGrid = new TreeGrid ();
        treeGrid.setWidth (300);
        treeGrid.setHeight (200);
        treeGrid.setSortFoldersBeforeLeaves (true);
        treeGrid.setSortDirection (SortDirection.ASCENDING);
        treeGrid.setFields (nameField);
        treeGrid.setData (tree);
        
        // handle expansion
        final TreeNode rootATreeNode = new TreeNode ();
        treeGrid.addFolderOpenedHandler (new FolderOpenedHandler () {
          public void onFolderOpened (final FolderOpenedEvent event) {
    
            // check children
            final TreeNode treeNode = event.getNode ();
            final TreeNode[] childArray = tree.getChildren (treeNode);
            final String childArrayDescription = childArray != null ?
              String.valueOf (childArray.length) + " elements" :
              "null";
            GWT.log ("onFolderOpened (). name: " + treeNode.getAttribute ("name") + ", " + childArrayDescription);
            
            // add children
            if (childArray == null || childArray.length == 0) {
              
              GWT.log ("onFolderOpened (). loading children because childArray is " + (childArray == null ? "null" : "empty"));
              final TreeNode childA = new TreeNode ();
              childA.setAttribute (tree.getIdField (), 3);
              childA.setAttribute (tree.getParentIdField (), 2);
              childA.setAttribute ("name", "Root A - Child A");
              tree.add (childA, rootATreeNode);
        
              final TreeNode childB = new TreeNode ();
              childB.setAttribute (tree.getIdField (), 4);
              childB.setAttribute (tree.getParentIdField (), 2);
              childB.setAttribute ("name", "Root A - Child B");
              tree.add (childB, rootATreeNode);
            }
          }
        });
        
        // Root A
        //   Root A - Child A
        //   Root A - Child B
        final IButton loadButton = new IButton ();
        loadButton.setTitle ("Load Root A");
        loadButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            rootATreeNode.setAttribute (tree.getIdField (), 2);
            rootATreeNode.setAttribute (tree.getParentIdField (), 1);
            rootATreeNode.setAttribute ("name", "Root A");
            rootATreeNode.setIsFolder (true);
            tree.add (rootATreeNode, rootTreeNode);
          }
        });
    
        // button to delete the root
        final IButton removeRootAButton = new IButton ();
        removeRootAButton.setTitle ("Remove Root A");
        removeRootAButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            tree.remove (rootATreeNode);
          }
        });
        
        // button to delete the root
        final IButton removeRootARecursiveButton = new IButton ();
        removeRootARecursiveButton.setTitle ("Remove Root A Recursive");
        removeRootARecursiveButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            removeTreeNodeExceptForRootTreeNode (tree, rootTreeNode);
          }
        });
    
        // button to dump the tree
        final IButton dumpTreeButton = new IButton ();
        dumpTreeButton.setTitle ("Dump Tree");
        dumpTreeButton.addClickHandler (new ClickHandler () {
          public void onClick (final ClickEvent event) {
    
            final TreeNode treeNode = tree.getRoot ();
            final String prefix = " ";
            dumpTreeNode (tree, treeNode, prefix);
          }
        });
    
        // layout
        final VLayout layout = new VLayout ();
        layout.addMember (treeGrid);
        layout.addMember (loadButton);
        layout.addMember (removeRootAButton);
        layout.addMember (removeRootARecursiveButton);
        layout.addMember (dumpTreeButton);
        layout.show ();
      }
    
      /**
       * Dumps the given TreeNode
       * @param tree
       * @param treeNode
       * @param prefix
       */
      private void dumpTreeNode (
        final Tree tree,
        final TreeNode treeNode,
        final String prefix) {
    
        // log
        GWT.log (
          prefix +
          "id: " + treeNode.getAttributeAsInt (tree.getIdField ()) +
          "parentId: " + treeNode.getAttributeAsInt (tree.getParentIdField ()) +
          "");
        
        // dump children
        final TreeNode[] childTreeNodeArray = tree.getChildren (treeNode);
        if (childTreeNodeArray != null) {
          for (final TreeNode childTreeNode : childTreeNodeArray) {
            dumpTreeNode (tree, childTreeNode, prefix + prefix);
          }
        }
      }
      
      /**
       * Removes the given TreeNode and all its children
       * @param tree
       * @param treeNode
       */
      private void removeTreeNodeExceptForRootTreeNode (
        final Tree tree,
        final TreeNode treeNode) {
        
        // remove children first
        final TreeNode[] childTreeNodeArray = tree.getChildren (treeNode);
        if (childTreeNodeArray != null) {
          for (final TreeNode childTreeNode : childTreeNodeArray) {
            removeTreeNodeExceptForRootTreeNode (tree, childTreeNode);
          }
        }
        
        // finally remove this node, unless it is the root
        if (treeNode.getAttributeAsInt (tree.getIdField ()) != 1) {
          tree.remove (treeNode);
        }
        else {
          GWT.log ("removeTreeNodeRecursive (): not deleting rootTreeNode");
        }
      }
    }
    Attached Files

    Comment


      #3
      I added an additional validation check by calling tree.hasChildren (treeNode) before adding children to it.

      After invoking tree.remove (treeNode); on "Root A", I added the following calls in onFolderOpened ():
      Code:
      final TreeNode[] childArray = tree.getChildren (treeNode);
      final Boolean hasChildren = tree.hasChildren (treeNode);
      In this test case I do this:
      1. Add Root A
      2. Expand Root A which adds 2 child nodes. At this point, the above invocations indicate that Root A has 0 children.
      3. Remove Root A (by either method)
      4. Add Root A
      5. Expand Root A to add 2 child nodes, but before it gets to add them, both of the above invocations indicate that Root A has 2 children...

      Am I doing this right?
      If anybody is removing treeNodes from a Tree, can you tell me how you are doing it?
      Any advice to help figure this out?

      Comment


        #4
        Ever get feedback on this?

        Has anyone reached out to you with info on this? I'm starting to wonder if I can trust SmartGWT's TreeGrid, etc.

        Comment


          #5
          This wasn't reproducible when we looked at it. Are you reproducing it with any current builds?

          Comment


            #6
            I just retested this with:
            SmartGWT 3.1p-2012-11-23
            SmartClient Version: v8.3p_2012-11-23/LGPL Development Only (built 2012-11-23)
            GWT 2.5.0
            FF 6.0.2

            And I am getting the expected result.

            With several previous builds including SmartGWT 3.0p-2012-04-26 and 3.0p-2012-08-31:
            1. Click Load Root A, which loads ONLY Root A
            2. Expand Root A, which loads the children. onFolderOpenend (), childArray is empty
            3. Close Root A
            4. Click Remove Root A
            5. Click Load Root A, which loads ONLY Root A
            6. Expand Root A, which does NOT load children because childArray is of size 2, which confirms that tree.remove () is not removing its children.

            With 3.1p-2012-11-23 step #6 does load the children because childArray is now of size 0. So this seems to work as expected to me.

            Comment

            Working...
            X