tree.select doesn't highlight the selected item

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.

  1. I have to give the item as a parameter not the object associated with the item class object return by addItem()
  2. 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 :


        t.addListener(new Component.Listener() {
            public void componentEvent(Component.Event event) {
                System.out.println(event.getClass().getName());
                System.out.println("selected: " + event.getSource().toString() + " (" + event.getSource().getClass().getName() + ")");
                Object o = t.getValue();
                System.out.println("selected = " + o);
            }
        });

Here is the output

As we can see, the first time a selection event is triggered (though still no visual update/highlight), then no more event

Got any ideas ?

Sincerely.
DKH

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
.

Thank you for your answer.

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 ?

Thank you all for your time.

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.

An attempt to clarify…

The row that says

t.select(container.getItem(currentContent));

should probably say

t.select(currentContent);

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).

Best Regards,
Marc

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.

what’s wrong ?

Thank you.

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();
        }
    }

my tree has this option :

tree.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_ITEM); 

do you see anything inherently wrong with the way I am doing this ?

Thanks a lot for your help !
Remi.

What is the effect you’re seeing now exactly?

How do your listeners look now, after the changes?

Best Regards,
Marc

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 ?

Sincerely.
Remi.

Could you post the selection code as it stands today? Specifically the code that looked like this earlier:

        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));
            }
        });

Best Regards,
Marc

Actually it isn’t that different


        t.addListener(new ItemClickEvent.ItemClickListener() {
            public void itemClick(ItemClickEvent itemClickEvent) {
                Content selectedContent = (Content) itemClickEvent.getItemId();
                System.out.println("User clicked on " + selectedContent.getTitle());
                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();
                }
                System.out.println("Programmatically selecting : " + currentContent);
                t.select(currentContent);
            }
        });

juste using the itemId insted of the item.

Thank you for your help.

FINALLY !

It works !

The trick was the method containsId.

I tried to locate the object inside the item list vs the itemId list

so in my case:


    public boolean containsId(Object o) {
        return itemByContent.values().contains(o);
    }

should read:


    public boolean containsId(Object o) {
        return itemByContent.keySet().contains(o);
    }

and now the highlight works.

I suspected something like that when using select() I had to pass an item and not an itemId for the tree to have a value (getValue() != null)

I think the culprit here is the naming used.
item and itemId are very confusing, at least to me.

I hope my mystake will help others in the future

thank you everybody for your time, it is really appreciated.