Hi guys,
First of all a huge thank you for vaadin, so I don’t have to deal with GWT directly.
Then, I am lazily loading treenodes. When I click on a node with children, I recursively open children till I find a leaf.
Then I usee tree.select(item) to set the selected item.
I have to give the item as a parameter not the object associated with the item class object return by addItem()
the item seems selected programatically, but visually the class v-tree-node-selected is not added to the node div.
My tree is built like this:
final Tree t = new Tree(s);
final LazyLoadingContainer container = new LazyLoadingContainer("42751");
t.setContainerDataSource(container);
t.setImmediate(true);
t.setSelectable(true);
t.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_ITEM);
t.addListener(new ItemClickEvent.ItemClickListener() {
public void itemClick(ItemClickEvent itemClickEvent) {
System.out.println(itemClickEvent.getItemId());
Content selectedContent = (Content) itemClickEvent.getItemId();
Content currentContent = selectedContent;
while (container.getChildren(currentContent).size() != 0) {
//find the first child and expand the node !
t.expandItem(currentContent);
currentContent = (Content) container.getChildren(currentContent).iterator().next();
}
t.select(container.getItem(currentContent));
}
});
some more info.
I am trying to select a random node every 5 seconds, the code is :
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
ArrayList<Content> items = new ArrayList<Content>((Collection<? extends Content>) container.getItemIds());
Item i = container.getItem(items.get(rand.nextInt(items.size())));
System.out.println("Selecting "+i);
t.select(i);
}
}
}.start();
I have also added a listener for every event on my tree like this :
Might this have something to do with the fact that you are updating the server-side state from another thread? As the server-side state is only sent as a response to a request, you need to poll the server for updates. You can do this for example with the
Refresher or with a
ProgressIndicator .
The thread selecting a node every 5 seconds was just a test, my real concern is that selecting a node in a tree doesn’t highlight the node though it is actually really selected (tree.getValue() return the programmatically selected item)
moreover you need to do a tree.setValue(null) before doing a setValue(item) otherwise after the first time, it doesn’t do anything (no real value setting + still no highlight)
Could there be a bug in here, where the programmatic select/setValue doesn’t trigger the same events as a simple user click on a specific node ?
select(Object itemId) should be passed the itemId, not the Item.
Also maybe I misunderstood your code but if your tree is selectable the tree node that is clicked by the user will automatically be selected, no need to separately use select(node) for that one.
And as previously stated, modifying the UI with a separate thread won’t update the UI in the browser, unless you poll - and make sure you synchronize on your application when updating the UI from the other thread(s).
Thank you for your answers but it’s more than it seems.
I guess I do not explain myself good enough.
here is a simple example :
public class Test extends com.vaadin.Application {
@Override
public void init() {
final Window main = new Window("Hello window");
setMainWindow(main);
main.setContent(generateTest());
}
private ComponentContainer generateTest() {
VerticalLayout vl = new VerticalLayout();
vl.setSizeFull();
final String[] nodes = new String[]
{"foo", "bar", "baz", "wiz", "fliz", "piz"};
final Tree tree = new Tree();
tree.setImmediate(true);
HierarchicalContainer container = new HierarchicalContainer();
tree.setContainerDataSource(container);
for (int i = 0; i < nodes.length; i++) {
container.addItem(nodes[i]
);
if ((i+1) % 2 == 0) {
container.setParent(nodes[i]
, nodes[i - 1]
);
container.setChildrenAllowed(nodes[i]
, false);
tree.expandItem(nodes[i - 1]
);
} else
container.setChildrenAllowed(nodes[i]
, true);
}
tree.setSelectable(true);
tree.setWidth("100%");
tree.setHeight("100%");
tree.addListener(new ItemClickEvent.ItemClickListener() {
public void itemClick(ItemClickEvent itemClickEvent) {
Random rand = new Random(System.currentTimeMillis());
System.out.println("Selected " + itemClickEvent.getItemId());
String obj;
do {
obj = nodes[rand.nextInt(nodes.length)]
;
} while (obj.equals(itemClickEvent.getItemId()));
System.out.println("Selecting now " + obj);
tree.select(obj);
}
});
vl.addComponent(tree);
return vl;
}
}
start the application then play with it and you will see that it doesn’t do what it is supposed to do, mainly, when selecting any node of the tree, it should automatically select/highlight another random one and it doesn’t.
After fixing the addItem() calls so they use nodes[i ]
you can indeed observe the behavior and funny as it sounds it is the expected behavior.
You have set the tree to selectable(true). This means that when you select a node using the mouse, a value change event will be sent to the server. This will update the value, return it to the client and highlight it.
You have additionally added an item click listener, which is triggered before the value change event. This means that AFTER you select whatever you select in the item click listener, the value change will be handled and the value will be updated (to the one you selected/clicked on).
I think there are two ways for you to solve this:
If you do not specifically need the item click listener, use a valuechangelistener. This will be triggered AFTER the automatic update of the value and your selection will stick. You need to be cautious however if you change the value inside the valuechangelistener. This will cause a call to the valuechangelistener and you might get an infinite loop calling valuechangelistener over and over again.
Set the tree to selectable(false). This only affects if the client can select a value. Then no value change event will be sent to the server and the value will not be updated after the click handler.
Great, this small example now works, but in my full context it doesn’t.
I am implementing my own container to lazy fetch the children of my nodes.
The only difference (that I can see so far) with the previous simple example, is the container.
It’s pretty big, but it’s pretty simple
class LazyLoadingContainer implements com.vaadin.data.Container.Hierarchical, com.vaadin.data.Container {
Book book = null;
Set<Content> roots = null;
HashMap<Content, Content> parentByChildId = new HashMap<Content, Content>();
HashMap<Content, Boolean> childrenAllowed = new HashMap<Content, Boolean>();
HashMap<Content, Item> itemByContent = new HashMap<Content, Item>();
LazyLoadingContainer(String bookId) {
book = bookDao.findByClebcId(bookId);
feedModel(null);
}
private Set<Content> feedModel(Content parent) {
Set<Content> children;
if (parent == null) {
if (roots == null)
roots = contentDao.findFirstLevelContentByBook(book);
children = roots;
} else
children = contentDao.findFirstLevelContentByContent(parent);
for (Content c : children) {
if (containsId(c)) continue; //do not add twice the same object, due to lazy loading
addItem(c); //the object we put in the tree, is the one we receive in the getChildren method
setChildrenAllowed(c, _hasChildren(c));
if (parent != null)
setParent(c, parent);
}
return (children);
}
private boolean _hasChildren(Content node) {
return (contentDao.findFirstLevelContentByContent(node).size() != 0);
}
public Collection getChildren(Object itemId) {
return feedModel((Content) itemId);
}
public Object getParent(Object o) {
return parentByChildId.get(o);
}
public Collection<?> rootItemIds() {
return roots;
}
public boolean setParent(Object child, Object parent) throws UnsupportedOperationException {
parentByChildId.put((Content) child, (Content) parent);
return true;
}
public boolean areChildrenAllowed(Object o) {
Boolean b = childrenAllowed.get(o);
return b == null || b.equals(Boolean.TRUE);
}
public boolean setChildrenAllowed(Object o, boolean b) throws UnsupportedOperationException {
childrenAllowed.put((Content) o, b);
return true;
}
public boolean isRoot(Object o) {
return roots.contains(o);
}
public boolean hasChildren(Object o) {
Boolean b = childrenAllowed.get(o);
return b == null || b.equals(Boolean.TRUE);
}
public Item getItem(Object o) {
return itemByContent.get(o);
}
public Collection<?> getContainerPropertyIds() {
return null;
}
public Collection<?> getItemIds() {
return itemByContent.keySet();
}
public Property getContainerProperty(Object itemId, Object property) {
return (null);
}
public Class<?> getType(Object o) {
return null;
}
public int size() {
return itemByContent.size();
}
public boolean containsId(Object o) {
return itemByContent.values().contains(o);
}
public Item addItem(Object itemId) throws UnsupportedOperationException {
Item i = new ContentItem((Content) itemId);
itemByContent.put((Content) itemId, i);
return i;
}
public Object addItem() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public boolean removeItem(Object o) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public boolean addContainerProperty(Object o, Class<?> aClass, Object o1) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public boolean removeContainerProperty(Object o) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
public boolean removeAllItems() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
class ContentItem extends PropertysetItem {
Content content;
ContentItem(Content content) {
this.content = content;
}
@Override
public String toString() {
return content.getTitle();
}
}
Currently when I click on a node, if it has children it will expand it, go to the first child, and so on until the first child has no child, then select it.
let’s say the tree is collapsed.
the behaviour I want is:
when I click on AAA it will expand itself, show BBB, then expand BBB, show CCC then select CCC because it has no child. Everything happen with a single click on AAA.
Now, the expanding is happening, but the selection part doesn’t work.
I do a tree.select() but the node/item is not highlighted
My listeners get the event, the correct item is clicked, the correct one is selected, but it doesn’t reflect on the tree.
Could it be because I have a custom Item base on PropertysetItem ?