6
votes

I have a block level element, a container, that should be hidden when all its child Wicket elements (buttons) are hidden. In other words, if any child button is visible, the container should be visible.

Earlier one of the buttons was always visible if any buttons were, so I used that button to control the visibility of a <wicket:enclosure>, handling all of this purely on HTML side.

Now, the specs have changed so that the buttons can be hidden/visible independently, so a simple enclosure won't work anymore (I think).

I got it working with something like this:

HTML:

<wicket:container wicket:id="downloadButtons">
     <wicket:message key="download.foo.bar"/>:
     <input type="button" wicket:id="excelDownloadButton" wicket:message="value:download.excel"/>
     <input type="button" wicket:id="textDownloadButton" wicket:message="value:download.text"/>
     <!-- etc ... -->
</wicket:container>

Java:

WebMarkupContainer container = new WebMarkupContainer("downloadButtons");

// ... add buttons to container ...

boolean showContainer = false;
Iterator<? extends Component> it = container.iterator();
while (it.hasNext()) {
    if (it.next().isVisible()) {
        showContainer = true;
        break;
    }
}
addOrReplace(container.setVisible(showContainer));

But the Java side is now kind of verbose and ugly, and I was thinking there's probably be a cleaner way to do the same thing. Is there? Can you somehow "automatically" hide a container (with all its additional markup) when none of its child components are visible?

(Wicket 1.4, if it matters.)

3

3 Answers

10
votes

If you want this to be reusable, you can define it as a IComponentConfigurationBehavior (for wicket version > 1.4.16) that you attach to any containers and then set the container visibility in the onConfigure() method of the behavior:

class AutoHidingBehavior extends AbstractBehavior {

    @Override
    public void bind(Component component) {
        if (! (component instanceof MarkupContainer) ) {
            throw new IllegalArgumentException("This behavior can only be used with markup containers");
        }
    }

    @Override
    public void onConfigure(Component component) {
        MarkupContainer container = (MarkupContainer) component;
        boolean hasVisibleChildren = false;
        for (Iterator<? extends Component> iter = container.iterator(); iter.hasNext(); ) {
            if ( iter.next().isVisible() ) {
                hasVisibleChildren = true;
                break;
            }
        }
        container.setVisible(hasVisibleChildren);
    }

}
4
votes

You could override the container's isVisible method to return true if any of the childs is visible (evaluating child visibility like you do now). This wouldn't reduce the code drastically but it would be 'nicer' in my eyes because the code determining visibility would be where it 'belongs'. You could make this a specialized container-class to further encapsulate the code.

Or you could subclass EnclosureContainer and add whatever visibility-logic you need.

Note: When overriding isVisible...

[...]be warned that this has a few pitfalls:

  • it is called multiple times per request, potentially tens of times, so keep the implementation computationally light

  • this value should remain stable across the render/respond boundary. Meaning if isVisible() returns true when rendering a button, but when the button is clicked returns false you will get an error

from Wicket in Action

0
votes

You can also use visitor.

In my case I have container with links in Panel. Code:

public abstract class MyPanel extends Panel
{
   private final WebMarkupContainer webMarkupContainer;

   public MyPanel(String id)
   {
      super(id);

      webMarkupContainer = new WebMarkupContainer("customContainer")
      {
         @Override
         protected void onBeforeRender()
         {
            super.onBeforeRender();
            boolean visible = Boolean.TRUE.equals(checkVisibleLinks());
            setVisible(visible);
         }
      };

      AjaxLink myLink = new AjaxLink("myLink")
      {
         @Override
         public void onClick(AjaxRequestTarget target)
         {
            //some action
         }

      };

      webMarkupContainer.add(myLink);
   }

   private Boolean checkVisibleLinks()
   {
      return webMarkupContainer.visitChildren(AbstractLink.class, new IVisitor<AbstractLink, Boolean>()
      {
         @Override
         public void component(AbstractLink link, IVisit<Boolean> visit)
         {
            if (link.isVisible())
            {
               visit.dontGoDeeper();
               visit.stop(true);
            }
         }
      });
   }

}