A way to "traverse" recursively all components of an application ?

We are looking for some experience on the following situation. any idea or feedback would be greatly appreciated.

in our design patterns we are trying to generalize on some of our “custom components” behaviours. In order to do this
we would like to be able to “traverse” recursively all the Components that make up a Vaadin Application
.

One of our design patterns requires to invoke certain methods on components (Windows, Panels, CustomComponents, Component), therefore the need for iterating recursively down the full tree of components. This include plain Vaadin components or our customcomponents.

Does the API provide an equivalent to DOM’s TreeWakler by which a traversal method can be selected, and a callback would be invoked.?
Is there a utility class - or at least an elegant method that you would know to do this ?

(a usage exemple. our custom components implements “Localizable” in order to easily change the appliaction locale on the fly, when the setLocale is called on the application we want to “traverse” all components recursively to execute a callback method to localize all captions and other in the application).

thanks in advance for your feedback.

E.

Hi,

You can easily traverse the component tree using the getComponentIterator() in ComponentContainer. For example:

// Build a user interface
catfinder();
        
// Traverse through all components (including the current one,
// which is a CustomComponent)
Stack<Component> stack = new Stack<Component>();
stack.push(this);
while (!stack.isEmpty()) {
    Component c = stack.pop();
    if (c instanceof ComponentContainer)
        for (Iterator<Component> i = ((ComponentContainer) c)
                .getComponentIterator(); i.hasNext();)
            stack.add(i.next());

    // Do something to each component
    if (c.getCaption() != null)
        c.setCaption("["+c.getCaption()+"]
");
    if (c instanceof Label)
        ((Label)c).setValue("\""+((String)((Label)c).getValue())+"\"");
}

See the
on-line example
.

You could use recursion just as well, etc. There’s no ready traversal class, but it should be trivial to make.

For the localization question, you would need to store a resource identifier in the component. You could put it in the data property of the component, with setData(), but that’s not so good if you need the data property for something else. Implementing a “Localizable” interface, which you suggested, is one solution I guess, although I’m a bit worried that it requires subclassing every component that you use, which is a bit awkward.

Perhaps you could use some translation storage that hashmaps a component to a resource id and manages those. For example:

TextField tf = new TextField();
translationStorage.manage(tf, MyTranslationsBundle.HELLO_WORLD);

A component decorator interface, if such is implement in future, might also help with this issue.

Hello,
We have implemented the following to traverse the tree. Indeed it’s relatively trivial yet in order to be comprehensive, it requires calling various method from various interfaces. Typically getWindows(), getContent(), getChildWindow(), getComponentIterator().
This piece of code is OK, but won’t evolve when Vaadin does. And maybe - who knows - it’s not complete (we faced the question with SplitPanel.getFirstComponent() and getSecondComponent() - they are properly iterated by getComponentIterator())
So the idea to have one TreeWalker in the core API…
Laurent


  /**
   * Traverse all components attached under theApplication.
   * @param theApplication
   * @param theInvocation
   */
  public void traverse(Application theApplication, MethodInvoker theInvocation) {
    // Self
    theInvocation.invokeMethod("Root", theApplication);
    // No invocation on self - application is not a component
    for (Window window : theApplication.getWindows()) {
      traverse(theApplication.toString(), window, theInvocation);
    }
  }

  /**
   * Traverse all components under a given componentContainer.
   * @param componentContainer
   * @param theInvocation
   */
  public void traverse(ComponentContainer componentContainer, MethodInvoker theInvocation) {
    traverse("Self", componentContainer, theInvocation);
  }

  private void traverse(String theCaller, ComponentContainer componentContainer, MethodInvoker theInvocation) {
    // Self
    theInvocation.invokeMethod(theCaller, componentContainer);

    // Content of Panel and children of Window
    if (componentContainer instanceof Panel) {
      final Panel panel = (Panel) componentContainer;
      if (panel.getContent() != null) {
        traverse(panel.toString(), panel.getContent(), theInvocation);
      }

      if (panel instanceof Window) {
        for (Window childWindow : ((Window) panel).getChildWindows()) {
          traverse(panel.toString(), childWindow, theInvocation);
        }
      }
    }
    // All the contained components
    final Iterator<Component> subComponents = componentContainer.getComponentIterator();
    while (subComponents.hasNext()) {
      final Component component = subComponents.next();
      if (component instanceof ComponentContainer) {
        traverse(componentContainer.toString(), (ComponentContainer) component, theInvocation);
      } else {
        theInvocation.invokeMethod(componentContainer.toString(), component);
      }
    }
  }

  /**
   * Just invoke any method on a component of the Application graph, just implement
   * the only method {@link #invokeMethod(String, Object)}.
   *
   * @version $Id: ComponentTraverser.java,v 1.11 2010/09/14 19:40:32 tettoni Exp $
   */
  public static interface MethodInvoker {
    /**
     * Invoke a method on theTarget.
     * @param theCaller Only for logging - not functional
     * @param theTarget The target object to invoke on.
     */
    public void invokeMethod(String theCaller, Object theTarget);
  }