Uploaded image for project: 'ZK'
  1. ZK
  2. ZK-1278

Unexpected callbacks of AbstractTreeModel

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Critical
    • Resolution: Fixed
    • Affects Version/s: 6.0.0
    • Fix Version/s: 6.5.2
    • Component/s: Components
    • Labels:
      None

      Description

      The callbacks isLeaf(), getChildCount(), getChild() are called after opening some nodes for non opened TreeItems.

      Example (s attachment):
      1. Open 'Sophis e' --> callbacks are ok
      2. Open Bond (child of 'Sophis e') --> callbacks for parent 'Sophis e' (maybe an error), callbacks for parent 'Bond' (ok), callbacks for parent 'Calypso' and its subtrees (is an error), callbacks for all other parents (is an error)

      The problem gets attention when the tree renders large data amounts.

      1. Logs.txt
        232 kB
        hshdev
      2. SimpleTreeTestModel.java
        1 kB
        hshdev
      1. Tree-Example.PNG
        8 kB

        Issue Links

          Activity

          Hide
          jimmyshiau jimmyshiau added a comment -

          Hi hshdev,

          To investigate this issue, I have created a model to log the timing where the APIs are invoked. I found that the API will only be called correctly, it will not invoke other parents. That is, I did not see the issue you reported.

          How did you verify that the APIs are called for all other parents? Can you provide me your code for proving this? I can take another look based on your code.

          BTW I have here included the sample that I used for logging:

          Unable to find source-code formatter for language: java. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
          package model.tree;
          
          import org.zkoss.zul.AbstractTreeModel;
          import org.zkoss.zul.TreeNode;
          
          import test.comp.TreeTestComposer.FileInfo;
          
          public class MyTreeModel<E> extends AbstractTreeModel<TreeNode<E>> {
          
          	public MyTreeModel(TreeNode root) {
          		super(root);
          	}
          
          	@Override
          	public boolean isLeaf(TreeNode<E> node) {
          		log(node, "isLeaf: ");
          		return node.isLeaf();
          	}
          
          	
          
          	@Override
          	public TreeNode<E> getChild(TreeNode<E> parent, int index) {
          		log(parent, "getChild: ");
          		return parent.getChildAt(index);
          	}
          
          	@Override
          	public int getChildCount(TreeNode<E> parent) {
          		log(parent, "getChildCount: ");
          		return parent.getChildCount();
          	}
          	
          	private void log(TreeNode<E> node, String prefix) {
          		if (node.getData() != null) {
          			FileInfo f =  (FileInfo) node.getData();
          			System.out.println(prefix + f.getPath());
          			
          		} else System.out.println(prefix + "null");
          		
          	}
          	
          }
          
          
          Show
          jimmyshiau jimmyshiau added a comment - Hi hshdev, To investigate this issue, I have created a model to log the timing where the APIs are invoked. I found that the API will only be called correctly, it will not invoke other parents. That is, I did not see the issue you reported. How did you verify that the APIs are called for all other parents? Can you provide me your code for proving this? I can take another look based on your code. BTW I have here included the sample that I used for logging: Unable to find source-code formatter for language: java. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml package model.tree; import org.zkoss.zul.AbstractTreeModel; import org.zkoss.zul.TreeNode; import test.comp.TreeTestComposer.FileInfo; public class MyTreeModel<E> extends AbstractTreeModel<TreeNode<E>> { public MyTreeModel(TreeNode root) { super (root); } @Override public boolean isLeaf(TreeNode<E> node) { log(node, "isLeaf: " ); return node.isLeaf(); } @Override public TreeNode<E> getChild(TreeNode<E> parent, int index) { log(parent, "getChild: " ); return parent.getChildAt(index); } @Override public int getChildCount(TreeNode<E> parent) { log(parent, "getChildCount: " ); return parent.getChildCount(); } private void log(TreeNode<E> node, String prefix) { if (node.getData() != null ) { FileInfo f = (FileInfo) node.getData(); System .out.println(prefix + f.getPath()); } else System .out.println(prefix + " null " ); } }
          Hide
          hshdev hshdev added a comment -

          Please use class SimpleTreeModel:
          Tree aggregationTree = (Tree) self.getFellow("aggregationTree");
          aggregationTree.setModel(new SimpleTreeTestModel());

          The tree definition in zul-file looks like this.
          <tree id="aggregationTree" zclass="z-dottree" autopaging="true" mold="paging"
          height="100%" width="100%" multiple="false" vflex="true"
          hflex="true" style="border:none" renderdefer="0">
          <treecols sizable="true">
          <treecol label="Column Lable"/>
          </treecols>
          </tree>

          The result is depicted in file 'Logs.txt'.
          Cheers,

          Show
          hshdev hshdev added a comment - Please use class SimpleTreeModel: Tree aggregationTree = (Tree) self.getFellow("aggregationTree"); aggregationTree.setModel(new SimpleTreeTestModel()); The tree definition in zul-file looks like this. <tree id="aggregationTree" zclass="z-dottree" autopaging="true" mold="paging" height="100%" width="100%" multiple="false" vflex="true" hflex="true" style="border:none" renderdefer="0"> <treecols sizable="true"> <treecol label="Column Lable"/> </treecols> </tree> The result is depicted in file 'Logs.txt'. Cheers,
          Hide
          jimmyshiau jimmyshiau added a comment -

          Hi hshdev,

          SimpleTreeModel was removed since ZK 6.0.0, what ZK version are you using?
          http://books.zkoss.org/wiki/Small_Talks/2011/November/ZK_6:_Upgrade_Notes#SimpleTreeModel_and_SimpleTreeNode_are_removed

          If you can tell me how you verified this issue it will be more helpful.

          Show
          jimmyshiau jimmyshiau added a comment - Hi hshdev, SimpleTreeModel was removed since ZK 6.0.0, what ZK version are you using? http://books.zkoss.org/wiki/Small_Talks/2011/November/ZK_6:_Upgrade_Notes#SimpleTreeModel_and_SimpleTreeNode_are_removed If you can tell me how you verified this issue it will be more helpful.
          Hide
          hshdev hshdev added a comment -

          We have not used the 'SimpleTreeModel'. Please look into file 'SimpleTreeTestModel.java'. There we use the 'AbstractTreeModel'.

          Show
          hshdev hshdev added a comment - We have not used the 'SimpleTreeModel'. Please look into file 'SimpleTreeTestModel.java'. There we use the 'AbstractTreeModel'.
          Hide
          jimmyshiau jimmyshiau added a comment -

          Hi hshdev,

          I finally found the reason!

          By default the Tree component will render TreeModel's data into Treeitems when you open a tree node. When rendering, Tree component will try to sync the open status of each Treeitem, and the Tree component will check whether the data's path has been opened by getPath API or not.

          And bascially the getPath API is implemented in the AbstractTreeModel, the implementation will go through each node to find the path (output an int array); when you open the Bond node ('Sophis e's child), the model will traverse each path of Bond node's children to sync open status, then go through each node in the model, it will then invoke isLeaf, getChildCount, getChild APIs several times.

          To fine tune the performance, you have to implement your getPath method.
          You can also refer to the following implementation for your "SimpleTreeModel":

          public int[] getPath(String child) {
          		char[] chars = child.toCharArray();
          		List<Integer> p = new ArrayList<Integer>();
          		for (int i = 0; i < chars.length; i++) {
          			char c = chars[i];
          			if (i == 0) {
          				if (c == 'A')
          					p.add(0);
          				else if (c == 'B')
          					p.add(1);
          				else if (c == 'C')
          					p.add(2);
          				else if (c == 'D')
          					p.add(3);
          				else if (c == 'E')
          					p.add(4);
          			} else {
          				p.add(Integer.parseInt(new Character(c).toString()));
          			}
          		}
          		
          		final Integer[] objs = p.toArray(new Integer[p.size()]);
          		final int[] path = new int[objs.length];
          		for (int i = 0; i < objs.length; i++)
          			path[i] = objs[i].intValue();
          		return path;
          	}
          }
          
          Show
          jimmyshiau jimmyshiau added a comment - Hi hshdev, I finally found the reason! By default the Tree component will render TreeModel's data into Treeitems when you open a tree node. When rendering, Tree component will try to sync the open status of each Treeitem, and the Tree component will check whether the data's path has been opened by getPath API or not. And bascially the getPath API is implemented in the AbstractTreeModel, the implementation will go through each node to find the path (output an int array); when you open the Bond node ('Sophis e's child), the model will traverse each path of Bond node's children to sync open status, then go through each node in the model, it will then invoke isLeaf, getChildCount, getChild APIs several times. To fine tune the performance, you have to implement your getPath method. You can also refer to the following implementation for your "SimpleTreeModel": public int [] getPath( String child) { char [] chars = child.toCharArray(); List< Integer > p = new ArrayList< Integer >(); for ( int i = 0; i < chars.length; i++) { char c = chars[i]; if (i == 0) { if (c == 'A') p.add(0); else if (c == 'B') p.add(1); else if (c == 'C') p.add(2); else if (c == 'D') p.add(3); else if (c == 'E') p.add(4); } else { p.add( Integer .parseInt( new Character (c).toString())); } } final Integer [] objs = p.toArray( new Integer [p.size()]); final int [] path = new int [objs.length]; for ( int i = 0; i < objs.length; i++) path[i] = objs[i].intValue(); return path; } }
          Hide
          simbo simbo added a comment -

          It would be helpful if the documentation of TreeModel could be updated to reflect this information that it is recommended that you override getPath to return a calculated path to root.

          Show
          simbo simbo added a comment - It would be helpful if the documentation of TreeModel could be updated to reflect this information that it is recommended that you override getPath to return a calculated path to root.
          Show
          jumperchen jumperchen added a comment - update - http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/MVC/Model/Tree_Model#Open_Tree_Nodes
          Hide
          jumperchen jumperchen added a comment -

          Bug fixed since 2/27/2013.

          We found a way to optimize the tree to solve this issue.

          Note that the getPath() method is good to override if the tree model contains a huge data.

          Show
          jumperchen jumperchen added a comment - Bug fixed since 2/27/2013. We found a way to optimize the tree to solve this issue. Note that the getPath() method is good to override if the tree model contains a huge data.

            People

            • Assignee:
              jumperchen jumperchen
              Reporter:
              hshdev hshdev
            • Votes:
              1 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: