Wicket container that is hidden when all its child components are hidden
Asked Answered
B

3

7

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

Budd answered 1/8, 2012 at 7:43 Comment(0)
P
11

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

}
Photophilous answered 1/8, 2012 at 10:0 Comment(1)
Nice; this approach was new to me, and now that I implemented it in our code, it's quite elegant too. (The original page is greatly simplified, and this promotes reuse.)Budd
T
4

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

Transubstantiation answered 1/8, 2012 at 8:35 Comment(4)
Thanks. About EnclosureContainer: you could use it yes, but to my understanding it wouldn't have any benefits over e.g. WebMarkupContainer here. Correct me if I'm wrong. (It wants one child component to control the visibility—if I had just one it would indeed be useful.)Budd
I was mainly looking for a way to avoid the somewhat ugly iteration code to determine container visibility—I had a hunch there'd be some way. But of course if that code is as clean as it gets, that's a valid answer too...Budd
+1: But it's probably better to subclass WebMarkupContainer and add the visibility check there. I wrote such a class just last week for exactly this reason.Raybourne
@Budd EnclosureContainer wouldn't have any benefits apart from a fitting place in the type hierarchy as it extends WebMarkupContainer on its own. Apart from the Iterator, the only other solution that comes to (my) mind would be a Visitor but I don't think that this would be a better fit.Transubstantiation
P
0

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

}
Pellmell answered 5/3, 2015 at 8:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.