1

I'm trying to create a link that will hide or show a part of my page. The link should be reusable and display one of two images, depending on state.

Adding the two subcomponents on every page where I use the link is kind of clunky so I wanted to create a component that behaves like a link while automatically adding its content.

This is the Link component:

public class ToggleVisibilityLink extends AjaxFallbackLink<Boolean>
{
  public ToggleVisibilityLink(final String id, final IModel<Boolean> model)
  {
    super(id, model);

    setOutputMarkupId(true);

    add(new Image("collapseImage")
    {
      @Override
      public boolean isVisible()
      {
        return !getModelObject();
      }
    });
    add(new Image("expandImage")
    {
      @Override
      public boolean isVisible()
      {
        return getModelObject();
      }
    });
  }

  @Override
  public void onClick(final AjaxRequestTarget target)
  {
    setModelObject(!getModelObject());
    if (target != null)
    {
      target.add(this);
      send(this.getParent(), Broadcast.EXACT, target);
    }
  }
}

And this is how I currently use it in HTML (this is added to the page or panel where I use the link):

<a href="#" wicket:id="collapseExpandLink" class="collapseExpandLink">
  <wicket:link>
    <img src="collapse.png" wicket:id="collapseImage" class="collapseExpandImage collapse">
  </wicket:link>
  <wicket:link>
    <img src="expand.png" wicket:id="expandImage" class="collapseExpandImage expand">
  </wicket:link>
</a>

And the corresponding Java call:

add(new ToggleVisibilityLink("collapseExpandLink", new PropertyModel(this, "hidden")));

But I want to be able to skip the body inside the link as one would have to know about the internals of ToggleVisibilityLink. I experimented with IMarkupResourceStreamProvider, using Dynamic markup in Wicket as a starting point. By googling I found another example where the poster was only able to get that to work when using a Panel, and I was able to do that as well. But I'd really like to keep the link and not package it inside a Panel, as I would not be able to style the link in the markup.

I'm also open to alternatives to encapsulate the link and its body.

Community
  • 1
  • 1
Aranian
  • 21
  • 4
  • Have you tried overriding `onComponentTagBody()`? – biziclop Nov 01 '12 at 00:41
  • You can create a reusable component with markup by creating a Panel. – Christoph Leiter Nov 01 '12 at 08:05
  • I thought about that (and now tried it, using `setBody()`, as Link already implements `onComponentTagBody()` to enable/disable links), but how would I render the images then? Specifically how would I get the URL of the images? Because I can render the HTML markup dynamically using this method (taking care of which image to show), but I need a `src` attribute. I thought about mounting them as a resource, but to completely encapsulate the component I'd like to avoid that. – Aranian Nov 01 '12 at 08:56
  • @ChristophLeiter: But then I would not be able to simply use it as a link, as I would have to put it into some sort of container or use `setRenderBodyOnly(true)`. Both of which have their Problems, e.g. attaching CSS classes and needing to implement an onClick() mechanism. – Aranian Nov 01 '12 at 09:02
  • @biziclop: I found the `urlFor(resourceReference, pageParameters)` method to retrieve the URL for my image. I'll have to fiddle a bit more with it though, as the URL provided is not quite correct. It appends a -ver-0_0_1 to the name and has a redundant /wicket in front... Thank you for the tip though! :) – Aranian Nov 01 '12 at 09:45

1 Answers1

1

I was able to get this to work using setBody(), even though I was trying to sabotage myself quite badly (I had duplicate libraries, my own incompatible jQuery library import and a custom resource versioning strategy).

Here is the current ToggleVisibilityLink:

public class ToggleVisibilityLink extends AjaxFallbackLink<Boolean>
{
  static {
    Application.get().getSharedResources().add("ToggleVisibilityLinkCollapse",
                                               new MyPackageResource(ToggleVisibilityLink.class, "collapse.png"));
    Application.get().getSharedResources().add("ToggleVisibilityLinkExpand",
                                               new MyPackageResource(ToggleVisibilityLink.class, "expand.png"));
  }

  public ToggleVisibilityLink(final String id, final IModel<Boolean> model)
  {
    super(id, model);

    setOutputMarkupId(true);
    setEscapeModelStrings(false);

    setBody(new BodyModel(model));
  }

  @Override
  public void onClick(final AjaxRequestTarget target)
  {
    setModelObject(!getModelObject());
    if (target != null)
    {
      target.add(this);
      send(this.getParent(), Broadcast.EXACT, target);
    }
  }

  private static final class BodyModel extends AbstractReadOnlyModel<String>
  {
    private final IModel<Boolean> model;

    private BodyModel(final IModel<Boolean> model)
    {
      this.model = model;
    }

    @Override
    public String getObject()
    {
      return this.model.getObject() ?
              "<img src=\""
            + RequestCycle.get().urlFor(new SharedResourceReference("ToggleVisibilityLinkExpand"), null)
            + "\" class=\"collapseExpandImage expand\">"
              :
              "<img src=\""
            + RequestCycle.get().urlFor(new SharedResourceReference("ToggleVisibilityLinkCollapse"), null)
            + "\" class=\"collapseExpandImage collapse\">";
    }
  }
}

Where MyPackageResource is a simple Implementation of PackageResource (why is that constructor protected?).

Then one can simply add the ToggleVisibilityLink to a container:

super.add(new ToggleVisibilityLink("collapseExpandLink", new PropertyModel(this, "hidden")));

and

<a wicket:id="collapseExpandLink" class="collapseExpandLink"></a>

and get notified via Event when the link is clicked.

Aranian
  • 21
  • 4