You found a bug! We've classified it as
We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.
SelectionModel of TreeGrid doesn't use equals()
SelectionModel of TreeGrid doesn't use equals()
I have ModelData instances, which overwrite equals() and hashCode(). In the TreeGrid, it works fine, but I have problems with the SelectionModel. You use == and != to compare instances within the methods onRemove(), doSelect() and onUpdate(). This will not work with my models, maybe it is better to compare the model instances with equals(). The onUpdate() method sets the member field lastFocused of the AbstractStoreSelectionModel class, but it is private (should be protected?). As workaround I have to use setLastFocused() on my sub-class, which fires an event however.
Thanks for the report - I agree that this could be a problem, but at the same time, in cases where the item in question is not actually in the store, there can be other unpredictable results. Because of that, we generally advise to use store.update to inform the store (and data widgets, selection models, etc) to use the latest version of that object. Can you describe your situation in a little more depth?
The task was: write a TreeGrid, which contains different data. So I have on the first level an entity called Institution, on the second level there are Locations and on the third level (the leafs) there are Processes.
We use a REST interface on the server side, there are specialized Proxies on client side, which provide access to the interface for the loaders. These things are implemented in libraries, which I have to use.
The idea was, I use three different loaders, which are accessed from a TreeGrid subclass, if the user expands a node. Depending on the level, the TreeGrid asks the associated loader for that level.
Ok, the loaders return instances of the specialized entity (i.e. Institution, Location, Process). To use it into the Tree, I have encapsulated the entities in another data class (called TreeModelData), which I use for the TreeGrid. I would not extend the base entities, because I use they on other places within the application and I don't need Tree functionality there.
Now I have a TreeGrid<TreeModelData>, where the TreeModelData wrap the ModelData entities. To simplify some code, I have decided, that unwrapped entities can be wrapped by new TreeModelData(entity), so you have unique entities, but an entity can be wrapped into different TreeModelData instances. So I haven't to handle unique TreeModelData within the application. I can use my entities and wrap they when it will be necessary for the tree.
This decision makes it hard to use HashMaps without re-implementation of equals() and hashCode(). So the TreeGrid must use equals() and hashCode() to use findModel() or something. The implementations of both methods delegate to the same methods of the wrapped entity. Here I have a further step: there are transient and persisted entities. The latter I can compare by an Id (which is unique and will be generated by the database), the transient entities can not be compared by its properties (they will be changed by the user), I have simply created a random number for every transient instance. There is no way to get a valid Id from the database before I have persisted the entity. But this is only possible, when all required properties are available.
This works fine with the HashMap of the TreeGrid, and the problem was, that you delegate some data to the selection model, where you compare the instances by != or ==. This will not work with the wrapper instances of TreeModelData.
As a workaround I have written a subclass of SelectionModel, which re-implements the critical methods. Only the lastFocused field I cannot set from the sub-class, so I have to use the setLastFocused() method, but there is a call inside this method, which fires an additional event. I can not say whether or not it is problematic.
A second problem I have identified, are transient entities, which I insert into the tree. If I try to set a selection on such a node, I expand the node. But during the expand operation you remove all the subnodes of such a node (you do this also recursively) and reloads it from the given loader. But my loader doesn't know the transient entity, it will simply removed from the TreeGrid. A following call to such a removed node will produce a NPE. It occurs within TreeGrid.setExpanded().
I have tried to overwrite the onLoad() of the TreeStore to save the transient models and restore it after super.onLoad(), but the event will not be executed before you access the node in TreeGrid.setExpanded(M model, boolean expand, boolean deep). Do you have an idea, how I can handle that? I would like to insert transient entities into the tree, if the user will generate a new one, it should be selected and displayed on an expanded node, then the user can modify some properties of the entity in another dialog.
Treating it like Java is definitely the right way forward - our == check is almost certainly wrong, but often if there are two .equal objects, the store should be given the most 'real' one via store.update, and everything else *should* update their copies of that object. Without trying your use case I can't be certain that this is the right way forward. Are you giving a key provider to the store? This should not only make it more efficient, but also will force it to simplify its notion of 'sameness' between an old model and a new one.
GXT 3 is much more internally consistent about these things - first, everything requires a string key, and in most places those should be used for checking 'sameness'.
One tip regarding subclassing classes with private members in GWT - you can use JSNI methods to read and write private fields, as well as private methods.
I've raised this internally, and we'll look into it for 2.3.1.