0

How to update UI for load on demand tree

asked 2015-02-12 17:29:11 +0800

rwoolf gravatar image rwoolf
35 5

updated 2015-02-17 22:25:13 +0800

I have implemented a load on demand tree by extending AbstractTreeModel. Once I have opened a branch node, and then I do some action to update the data behind that branch, it is not updating the UI to reflect the changes (something added or deleted in the branch). I have to refresh the whole tree to be able to see the updated data. This update to the tree causes all opened branches to collapse again.

Can someone point me to where there is an example, or explain to me how I can get branch nodes to update once they have been populated in the tree but the underlying data has changed and I need to update the UI to reflect that.

Added information as per request... Portion of zul with tree:

    <?init class="com.tybera.dms.auth.AuthenticationInit"?>
<zk><div vflex="1" apply="org.zkoss.bind.BindComposer"
      viewModel="@id('sm') @init('com.tybera.dms.ui.model.SearchModel')">
  <div vflex="1" apply="org.zkoss.bind.BindComposer"
          viewModel="@id('btm') @init('com.tybera.dms.ui.model.DMSTreeModel')">
    <tabbox vflex="true" hflex="true">
      <tabs>
        <tab label="${labels.home.explorer}" vflex="true"
          hflex="true"
          onSelect='@global-command("globalSelectedItem", selectedItem=btm.selectedItem)' />
        <tab label="${labels.home.search}"
          onSelect='@global-command("globalSelectedItem", selectedItem=sm.selectedItem)' />
      </tabs>
      <tabpanels>
        <tabpanel vflex="true" hflex="true">
          <borderlayout sclass="complex-layout" vflex="true"
            hflex="true">
            <west id="west" width="500px" border="0"
              hflex="true" splittable="true" margins="0,5,0,0">
              <tree id="tree" vflex="true" model="@load(btm)"
                zclass="z-filetree" selectedItem="@save(btm.selectedItem)"
                onSelect='@global-command("globalSelectedItem", selectedItem=btm.selectedItem)'
                rows="0">
                <treecols sizable="true">
                  <treecol label=" " width="100%" />
                </treecols>
                <template name="model" var="item">
                  <treeitem>
                    <treerow>
                      <treecell
                        label="${item.name}">
                        <zk
                          if='${item.isLeaf=="true"}'>
                          <label
                            if='${!item.description.equals("")}'
                            value="- ${item.description}" />
                        </zk>
                      </treecell>
                    </treerow>
                  </treeitem>
                </template>
                <treefoot>
                  <!--                                  <treefooter label="Count" />-->
                </treefoot>
              </tree>
            </west>
            ...

DMSTreeModel which extends AbstractTreeModel. In the updateTree method I just have some hard coded stuff to get the path. At this point I only add items to this location in the tree to see if I can get the UI to update. Once I have it working I will modify code to figure out what the path is for other scenarios.

import java.util.HashMap;

import org.zkoss.bind.BindUtils; import org.zkoss.bind.annotation.BindingParam; import org.zkoss.bind.annotation.GlobalCommand; import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.zul.AbstractTreeModel; import org.zkoss.zul.event.ListDataEvent; import org.zkoss.zul.ext.TreeSelectableModel;

import com.tybera.dms.ui.data.DMSTreeData;

public class DMSTreeModel extends AbstractTreeModel implements TreeSelectableModel {

/**
 * 
 */
private static final long serialVersionUID = 4595052039145500261L;

private DMSTreeData selectedItem = null;

public DMSTreeData getSelectedItem() {
    return selectedItem;
}

public void setSelectedItem(DMSTreeData selectedItem) {
    this.selectedItem = selectedItem;
}

public DMSTreeModel() {
    super(new DMSTreeData());
}

public Object getChild(Object parent, int index) {
    return new DMSTreeData(parent, index);
}

public int getChildCount(Object parent) {
    return ((DMSTreeData) parent).getChildCount();
}

public boolean isLeaf(Object node) {
    return ((DMSTreeData) node).isLeaf();
}

/**
 * @since 5.0.6
 * @see org.zkoss.zul.TreeModel#getIndexOfChild(java.lang.Object,
 *      java.lang.Object)
 */
@Override
public int getIndexOfChild(Object arg0, Object arg1) {
    return 0;
}

@GlobalCommand("viewDoc")
public void viewDoc(@BindingParam("id") String id,
        @BindingParam("version") String version,
        @BindingParam("tabName") String tabName,
        @BindingParam("repo") String repo) {
    if (id != null && id.trim().length() > 0) {
        HashMap<String, Object> hashmap = new HashMap<String, Object>(3);
        hashmap.put("name", tabName);
        StringBuffer strBufSrc = new StringBuffer("docView.zul?id="+id+"&repo="+repo); ///NEED to append repository here
        if (version != null && version.trim().length() >0)
            strBufSrc.append("&version="+version);
        hashmap.put("src", strBufSrc.toString());
        hashmap.put("multiple", false);
        hashmap.put("relatedTo", id);
        BindUtils.postGlobalCommand(null, null, "addTab", hashmap);
    }
}

@GlobalCommand("updateTree")
public void updateTree(){
    final int[] path = new int[2];
    path[0]=1;
    path[1]=1;
            //hard coded path for now.  In testing I only add to this path.
    Object node = this.getChild(path);
    BindUtils.postNotifyChange(null, null, node, "*");
}

@Override
public boolean addOpenObject(Object child) {
    final int[] path = new int[2];
    path[0]=1;
    path[1]=0;
    if (path != null && path.length > 0)
        return addOpenPath(path);
    return false;
}

}

delete flag offensive retag edit

Comments

MVC or MVVM?

chillworld ( 2015-02-13 06:27:31 +0800 )edit

My application is using MVVM.

rwoolf ( 2015-02-13 16:50:12 +0800 )edit

show me the zul and viewmodel (part where you change the object and part of the updatetree)

chillworld ( 2015-02-13 21:59:52 +0800 )edit

I added the requested code to the original message. I have played around with various things but still unable to get the postNotifyChange to do anything.

rwoolf ( 2015-02-17 23:13:26 +0800 )edit

2 Answers

Sort by ยป oldest newest most voted
2

answered 2015-02-25 06:38:08 +0800

hawk gravatar image hawk
3250 1 5
http://hawkphoenix.blogsp... ZK Team

updated 2015-02-25 06:51:16 +0800

You can use either model.fireEvent(TreeDataEvent.INTERVAL_ADDED, model.getPath(selectedItem) , index, index) or @NotifyChange("model") to update the tree in a browser. But first way has better performance.

Notice that fireEvent()'s 2nd parameter is the path of newly-added node's parent node, you can get a node's path with AbstractTreeModel.getPath().

Here is a simple example to demonstrate the idea:

<div apply="org.zkoss.bind.BindComposer"
    viewModel="@id('vm') @init('support.component.tree.TreeEventVM')">
    <button onClick="@command('add')">add</button>
    <tree model="@bind(vm.model)" selectedItem="@save(vm.selectedItem)">
        <template name="model">
            <treeitem>
                <treerow>
                    <treecell label="@load(each)"></treecell>
                </treerow>
            </treeitem>
        </template>
    </tree>
</div>

@Command 
// @NotifyChange("model") simple but cost more effort for it renders the whole tree again 
public void add(){
    if (selectedItem != null){
        MyNode newChild = new MyNode(Calendar.getInstance().getTime().toString());
        selectedItem.getChildren().add(newChild);
        int index = selectedItem.getChildren().indexOf(newChild);
        //fire an event upon situations, please refer to TreeDataEvent for the complete list of tree data events
        model.fireEvent(TreeDataEvent.INTERVAL_ADDED, model.getPath(selectedItem) , index, index);
        // no effect for no binding on root
        //      BindUtils.postNotifyChange(null, null, model.getRoot(), "*"); 
    }
}

When using a tree model, you should consider ZK's default tree model and node class first, then replace some of them with your own according to the complexity of your requirement. DefaultTreeModel and DefaultTreeNode will fire events automatically for you. But implement your own tree model, you should fire events by yourselves.

link publish delete flag offensive edit

Comments

This example helped me to sort out where I was going wrong. Once I started using INTERVALADDED instead of CONTENTSCHANGED then the tree would finally update. I also discovered that the ZK 6.5.2 that I was using had a defect that was causing the null pointer error to be thrown. 6.5.4 fixed that.

rwoolf ( 2015-03-02 17:53:47 +0800 )edit
0

answered 2015-02-13 18:40:13 +0800

chillworld gravatar image chillworld flag of Belgium
5367 4 9
https://github.com/chillw...

updated 2015-02-18 06:59:20 +0800

Then when your object is changed just do :

BindUtils.postNotifyChange(null,null,mineObject,"*");

Where mineObject is not the model of the tree but an object in the model of the tree.

Edit:

Try using this method :

fireEvent(int evtType, int[] path, int indexFrom, int indexTo);

This is a method from the AbstractTreeModel what should do the trick.

Greetz chill.

link publish delete flag offensive edit

Comments

I tried this but it does not work. In MyTreeModel class, which extends AbtractTreeModel I have a command "updateTree" that calls postNotifyChange. I obtain "mineObject" by using this.getChild(path) path to the "folder" that its contents have changed. But no update occurs when called. Ideas?

rwoolf ( 2015-02-13 21:20:21 +0800 )edit

I have tried fireEvent(ListDataEvent.CONTENTS_CHANGED,path, 0, 1); to no success. It produces a null pointer error deep within ZK. As for indexFrom and indexTo, I'm not exactly sure what this is asking for. I have played with all sorts of values. What exactly are these values?

rwoolf ( 2015-02-23 18:24:38 +0800 )edit

maybe thsi is the reason : Fires a TreeDataEvent for all registered listener Note: you can invoke this method only in an event listener. are you in an eventlistener?

chillworld ( 2015-02-23 21:07:07 +0800 )edit

This makes no sense. The method call to fire the event has to live inside the listener of the event? I tried it but as suspected it never gets called because I have no way to trigger an event to cause the listener to be invoked and make this call to fire the event. Chicken/egg scenario. Code Sample?

rwoolf ( 2015-02-23 22:29:38 +0800 )edit

There must be some way to fire an event outside of a listener. Otherwise how can you ever raise the event that causes the listeners to be called?

rwoolf ( 2015-02-23 22:35:13 +0800 )edit
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow
1 follower

RSS

Stats

Asked: 2015-02-12 17:29:11 +0800

Seen: 62 times

Last updated: Feb 25 '15

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More