Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test if <jsp:doBody/> is empty / null in a tag file?

Tags:

jsp

jsp-tags

I discover jsp tags recently and use them to avoid duplication of common part of my views.

So in my JSP views I have

<web-component:mytag>
<!-- HTML specific to that page -->
</web-component:mytag>

And in my tag file I get the HTML with <jsp:doBody/>.

My question is, how can I test if <jsp:doBody/> is empty so I can put a default HTML content?

Something like

<c:choose>
    <c:when test="${!doBody.empty}">
        <jsp:doBody/>
    </c:when>
    <c:otherwise>
    <!-- My default HTML content here -->
    </c:otherwise>
</c:choose>

So I'm looking for the correct expression insted of doBody.empty

Thanks in advance.

like image 791
Fla Avatar asked Aug 05 '14 08:08

Fla


1 Answers

You have three options available to you, each with its pros and cons.

1. <jsp:doBody var="bodyText" />

This is probably the most common and obvious solution.

You execute the body once, store its output in a variable, and check to see if that variable is empty.

<jsp:doBody var="bodyText"/>
<c:choose>
  <c:when test="${not empty fn:trim(bodyText)}">
    ${bodyText}
  </c:when>
  <c:otherwise>
    <!-- Default content here -->
  </c:otherwise>
</c:choose>

Why use ${not empty fn:trim(bodyText)} instead of the simpler condition ${empty bodyText}? Thanks to @ach, who pointed this out, the latter trims whitespace and thus behaves consistently regardless of the trimDirectiveWhitespaces setting.

Pros

  • Being the canonical solution, it is likely the least surprising.

Cons

  • The body always gets executed. You may not want to execute the tag body (and any side effects) just to test for its existence.

  • The entire contents of the tag body get stored in memory. This could be an issue for longer tag bodies.

  • (Disregard this point when using ${not empty fn:trim(bodyText)}. It only applies to ${empty bodyText}.)

    Whether the condition ${empty bodyText} is true or not depends on whether the calling JSP enables trimDirectiveWhitespaces.

    When the tag's body contains nothing but whitespace

    <%@ page trimDirectiveWhitespaces="true" %>
    <my:tag value="a">
    </my:tag>
    

    and the page defines trimDirectiveWhitespaces="true", your tag file will detect that there is no body. If trimDirectiveWhitespaces="false", your tag file will detect that there is a body.

    Note that this only applies to tag files, not simple/classic tag handlers. In the above example, if <my:tag> were defined in a tag handler, the handler would behave as if a body exists regardless of the whitespace setting:

    • For simple tag handler, getJspBody() would be non-null.
    • For a classic tag handler, doAfterBody() would be called.

2. Scriptlet

You can do away with the cons of the first solution by testing for null body with Java code. This solution and the next do exactly that, except this one is simpler and uses a scriptlet.

<c:choose>
  <c:when test="<%= super.getJspBody() != null %>">
    <jsp:doBody />
  </c:when>
  <c:otherwise>
    <!-- My default HTML content here -->
  </c:otherwise>
</c:choose>

The reason this works is that tag files are translated into handlers extending SimpleTagSupport (see javax.servlet.jsp.tagext) and scriptlets are executed within the handler's doTag() method. Thus super.getJspBody() == null works the same as you'd expect in any simple tag handler.

Pros

  • The test for a null body is done without executing the body.

  • The body is not stored in a variable.

Cons

  • Use of scriptlets. (But note, tag files are allowed to use scriptlets regardless of the scripting-invalid configuration for JSP files.)

3. Helper tag

And if you need the functionality of the scriptlet solution without actually using scriptlets, read on.

The idea here is to encapsulate the body null test within a tag handler called from the tag file. The tag handler can get access to the tag file via getParent() and and export the boolean test result as a variable:

class HasBody extends SimpleTag {
  @Override
  public void doTag() throws JspException {
    TagAdapter adapter = (TagAdapter)this.getParent();
    SimpleTagSupport parent = (SimpleTagSupport)adapter.getAdaptee();

    // `getJspBody()` is a protected method, so actually you must use reflection.
    JspFragment body = parent.getJspBody();

    this.getJspContext().setAttribute("hasBody", body == null);
  }
}

Now your tag file can look like this:

<my:hasBody />
<c:choose>
  <c:when test="${hasBody}">
    <jsp:doBody />
  </c:when>
  <c:otherwise>
    <!-- Default content here -->
  </c:otherwise>
</c:choose>

Pros

  • Does not use scriptlets.
  • Functionally equivalent to the scriptlet solution.

Cons

  • Complicated. Requires a separate tag handler just to perform the body null test.
  • Requires use of reflection.

Addendum: Use with Non-Body JSPFragments

@Ryan was wondering about adapting this approach to general JSPFragment attributes. Luckily, because all attributes are directly accessible as variables from EL (whereas the body is not), testing for an empty fragment attribute is trivial:

<%@ attribute name="frag" fragment="true" %>
<c:choose>
  <c:when test="${frag != null}">
    <jsp:invoke fragment="frag" />
  </c:when>
  <c:otherwise>
    <!-- Default content here -->
  </c:otherwise>
</c:choose>
like image 148
mxxk Avatar answered Oct 25 '22 16:10

mxxk