Version of Ext GWT
Ext GWT 3.1.1

Browser version and OS
Firefox 36.0, Ubuntu 12.04

Description
When a node is updated with a new reference which has the same key, then its children are not well displayed anymore.
If the node children were not displayed before the update, then after the node expand the children are displayed over the node.
If the node children were displayed before the update, then the node can not be expanded/collapsed anymore.

Step to reproduce
  1. Sample code below:
Code:
public class App implements EntryPoint {
  
  @Override
   public void onModuleLoad() {
     Viewport viewport = new Viewport();
     RootLayoutPanel.get().add(viewport);
 
     final TreeStore<Data> store = new TreeStore<App.Data>(new KeyProvider());
     final Data root = new Data("0", "root");
     final Data child = new Data("1", "child");
 
     store.add(root);
     store.add(root, child);
 
     ColumnConfig<Data, String> name = new ColumnConfig<Data, String>(Data.props.name());
     name.setHeader("Name");
 
     List<ColumnConfig<Data, ?>> l = new ArrayList<ColumnConfig<Data,?>>();
     l.add(name);
 
     ColumnModel<Data> cm = new ColumnModel<Data>(l);
 
     final TreeGrid<Data> tree = new TreeGrid<Data>(store, cm, name);
 
     ToolBar bar = new ToolBar();
     bar.add(new TextButton("Update root", new SelectHandler() {
 
       @Override
       public void onSelect(SelectEvent event) {
         Data root2 = new Data("0", "root2"); // new reference here
         store.update(root2);
         tree.setExpanded(root2, true);
       }
     }));
 
     VerticalLayoutContainer v = new VerticalLayoutContainer();
     v.add(bar, new VerticalLayoutData(1, -1));
     v.add(tree, new VerticalLayoutData(1, 1));
     viewport.add(v);
   }
 
   private static class KeyProvider implements ModelKeyProvider<Data> {
 
     @Override
     public String getKey(Data item) {
       return item.id;
     }
  }
 
   public static class Data {
     String id;
     String name;

     public Data(String id, String name) {
       this.id = id;
       this.name = name;
     }
 
     public static Properties props    = GWT.create(Properties.class);
     public interface Properties extends PropertyAccess<Data> {
       ValueProvider<Data, String> name();
     }

     public String getName() {
       return name;
     }
   }
}
  1. Click on the “Update root” button to reproduce the bug.

Cause
The model reference in TreeNode in the TreeGrid class is not updated after the store update, while it is in the Tree class.

Tree.onUpdate code
Code:
  protected void onUpdate(StoreUpdateEvent<M> event) {
    for (M item : event.getItems()) {
      TreeNode<M> node = findNode(item);
      if (node != null) {
        if (node.model != item) {
          node.model = item;
        }
        refresh(item);
      }
    }
  }
TreeGrid.onUpdate code
Code:
  protected void onUpdate(StoreUpdateEvent<M> se) {
    for (M m : se.getItems()) {
      // looking up by key instead of by index to allow for different model instance, but no equals() equality
      if (store.findModelWithKey(store.getKeyProvider().getKey(m)) != null) {
        store.update(m);
      }
    }
  }
Work around
Add this reference update in a custom TreeGrid class. A new TreeGridNode class is necessary, because the model attribute in TreeNode is private.

Code:
public class CustomTreeGrid<M> extends TreeGrid<M> {
 
   public static class TreeGridNode<M> extends TreeNode<M> {
 
     private M model;
 
     protected TreeGridNode(String modelId, M m, String domId) {
       super(modelId, m, domId);
       this.model = m;
     }
 
     @Override
     public void clearElements() {
       super.clearElements();
       setContainerElement(null);
       setElContainer(null);
       element = null;
     }
 
     @Override
     public M getModel() {
       return model;
     }
  }

   public CustomTreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn) {
     super(store, cm, treeColumn);
   }
 
   @Override
   protected void onUpdate(StoreUpdateEvent<M> se) {
     super.onUpdate(se);
 
     for (M item : se.getItems()) {
       TreeGridNode<M> node = (TreeGridNode<M>) findNode(item);
       if (node != null) {
         if (node.model != item) {
           node.model = item;
         }
         refresh(item);
       }
     }
   }
 
   @Override
   protected String register(M m) {
     String id = generateModelId(m);
     if (!nodes.containsKey(id)) {
       String domId = XDOM.getUniqueId();
       TreeGridNode<M> node = new TreeGridNode<M>(id, m, domId);
       nodes.put(id, node);
       nodesByDomId.put(domId, node);
     }
     return id;
   }
}
Arnaud Leloup