Creating a Micro-View with JSP Taglibs

Originally published: 2009-03-18

Last updated: 2009-03-18

The MVC — model-view-controller — approach to web development is based on the idea that a JSP page should contain only presentation code; it should not contain any business logic. Instead, a controller object retrieves data and applies any business logic to build a model, then passes that model to the page (the view), which then generates HTML.

All well and good, but it suffers from the problem that real-world data is complex, and as a result logic begins to creep into the view to tweak the presentation. Perhaps that logic is implemented using JSTL, but in my mind there's little difference between a JSTL c:forEach and a Java for loop in a scriptlet. Actually, there is a difference: JSTL takes imperative control structures and forces them into the declarative syntax of XML.

An Alternative: “Micro-Views”

Rather than create a complex model and then pick it apart in the view, my preference is to create a simple model, with a few key data elements, that can then be used as inputs to JSP tags. These tags in turn create their own models, that are accessible only within the tag's body. I use the term “micro-view” to highlight the limited lifetime of this model (and because “micro-model” and “micro-controller” don't sound right).

This is best described by example: generating the price display for a product on an eCommerce site. If an item is on sale, you want to display both the sale and regular price; it it's not on sale, you just want to display the regular price, but with different styling. Using a micro-view tag, the markup might look like this:

<pricing:setup>
    <pricing:regular>
        <div class="regPrice">Price: ${regPrice}</div>
    </pricing:regular>
    <pricing:onSale>
        <div class="regPriceOnSale">Regular Price: ${regPrice}</div>
        <div class="salePrice">Now: ${salePrice}</div>
    </pricing:onSale>
</pricing:setup>

Here I've created three different tags: pricing:setup, pricing:regular, and pricing:onSale. The first of these is responsible for storing the values regPrice and salePrice (and perhaps others) into the page context, making them available to the tag body. For many micro-views, a single tag with HTML body is sufficient.

Here, since we have a conditional display, I created the other two tags to implement the condition in a declarative way. Depending on whether the item is or is not on sale, one of these tags will do nothing; the other will put its body into the page.

For comparison, here is a JSTL-based implementation. It takes about the same amount of code, but requires that a priceData bean has already been stored in the page context. More importantly, it puts business logic — the knowledge of what defines an on-sale product — into the page.

<c:choose>
    <c:when test="${empty priceData.salePrice}">
        <div class="regPrice">Price: ${priceData.regPrice}</div>
    </c:when>
    <c:when test="${!empty priceData.salePrice}">
        <div class="regPriceOnSale">Regular Price: ${priceData.regPrice}</div>
        <div class="salePrice">Now: ${priceData.salePrice}</div>
    </c:when>
</c:choose>

Why Not Do It All In The Taglib?

Taglibs are often used to generate output directly; I've done that myself on many occasions. However, every time that you do that, you're mixing markup and business logic. Philosophically, it's no different than putting logic in your pages. In practice it's worse, because the markup is hidden in a mass of print statements, and changes require a build-deploy cycle.

A bigger problem with tags that emit markup is that they're not adaptable. Going back to the price example: a typical eCommerce product page will show both a main product and subsidiary products such as accessories. While you typically want to highlight that the main product is on sale, it's not so important with the accessories, which typically have a small area of the page.

If your tags emit markup, you must either create a new single-purpose tag for accessories, or pass a flag that controls the markup — and that will quickly become a mess, as you discover additional places that need unique markup. With micro-views, you just change the view:

<pricing:setup>
    <pricing:regular>
        <div class="price">Price: ${regPrice}</div>
    </pricing:regular>
    <pricing:onSale>
        <div class="price">Price: ${salePrice}</div>
    </pricing:onSale>
</pricing:setup>

Some Thoughts on Data Management

My implementation approach is to store data in the page context in doStartTag(), and remove it in doEndTag(). This approach has its limitations: for example, although the tag body could contain a dynamic include, the data wouldn't be available to the included file. While you could make this work by scoping your view data to the request context, I believe this goes against the philosophy of a “micro” view: if you're loading a new page within the view, you might as well use a controller servlet.

Similarly, I believe that the view tags should clean up after themselves: doEndTag() should remove any items added to the context. In most cases, it won't matter, but sooner or later you'll find yourself displaying the wrong value somewhere else on the page, with no idea how that value got set.

For More Information

A version of the pricing tags described in this article are available here. These are commented, and may be useful as a start for your own implementation, but they don't actually do anything.

A more useful example is dumpRequest, a JSP page that displays information about the current context. The jarfile contains both my original version of this page, which used scriptlets to iterate the various values, as well as the new version using micro-views within iterator tags.

Both of these examples are meant to be unpacked into an existing Netbeans WebApp project.

Copyright © Keith D Gregory, all rights reserved

This site does not intentionally use tracking cookies. Any cookies have been added by my hosting provider (InMotion Hosting), and I have no ability to remove them. I do, however, have access to the site's access logs with source IP addresses.