320

At work I've been tasked with turning a bunch of HTML files into a simple JSP project. It's really all static, no serverside logic to program. I should mention I'm completely new to Java. JSP files seem to make it easy to work with common includes and variables, much like PHP, but I'd like to know a simple way to get something like template inheritance (Django style) or at least be able to have a base.jsp file containing the header and the footer, so I can insert content later.

Ben Lings seems to offer some hope in his answer here: JSP template inheritance Can someone explain how to achieve this?

Given that I don't have much time I think dynamic routing is a little much, so I'm happy to just to have URLs map directly onto .jsp files, but I'm open to suggestion.

Thanks.

edit: I don't want to use any external libraries, because it would increase the learning curve for myself and others who work on the project, and the company I work for has been contracted to do this.

Another edit: I'm not sure if JSP tags will be useful because my content doesn't really have any template variables. What I need is a way to be able to do this:

base.html:

<html><body>
{ content.body }
</body></html>

somepage.html

<wrapper:base.html>
<h1>Welcome</h1>
</wrapper>

with the output being:

<html><body>
<h1>Welcome</h1>
</body></html>

I think this would give me enough versatility to do everything I need. It could be achieved with includes but then I would need a top and a bottom include for each wrapper, which is kind of messy.

Community
  • 1
  • 1
Scott
  • 4,070
  • 3
  • 21
  • 16

6 Answers6

719

As skaffman suggested, JSP 2.0 Tag Files are the bee's knees.

Let's take your simple example.

Put the following in WEB-INF/tags/wrapper.tag

<%@tag description="Simple Wrapper Tag" pageEncoding="UTF-8"%>
<html><body>
  <jsp:doBody/>
</body></html>

Now in your example.jsp page:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:wrapper>
    <h1>Welcome</h1>
</t:wrapper>

That does exactly what you think it does.


So, lets expand upon that to something a bit more general. WEB-INF/tags/genericpage.tag

<%@tag description="Overall Page template" pageEncoding="UTF-8"%>
<%@attribute name="header" fragment="true" %>
<%@attribute name="footer" fragment="true" %>
<html>
  <body>
    <div id="pageheader">
      <jsp:invoke fragment="header"/>
    </div>
    <div id="body">
      <jsp:doBody/>
    </div>
    <div id="pagefooter">
      <jsp:invoke fragment="footer"/>
    </div>
  </body>
</html>

To use this:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:genericpage>
    <jsp:attribute name="header">
      <h1>Welcome</h1>
    </jsp:attribute>
    <jsp:attribute name="footer">
      <p id="copyright">Copyright 1927, Future Bits When There Be Bits Inc.</p>
    </jsp:attribute>
    <jsp:body>
        <p>Hi I'm the heart of the message</p>
    </jsp:body>
</t:genericpage>

What does that buy you? A lot really, but it gets even better...


WEB-INF/tags/userpage.tag

<%@tag description="User Page template" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@attribute name="userName" required="true"%>

<t:genericpage>
    <jsp:attribute name="header">
      <h1>Welcome ${userName}</h1>
    </jsp:attribute>
    <jsp:attribute name="footer">
      <p id="copyright">Copyright 1927, Future Bits When There Be Bits Inc.</p>
    </jsp:attribute>
    <jsp:body>
        <jsp:doBody/>
    </jsp:body>
</t:genericpage>

To use this: (assume we have a user variable in the request)

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:userpage userName="${user.fullName}">
  <p>
    First Name: ${user.firstName} <br/>
    Last Name: ${user.lastName} <br/>
    Phone: ${user.phone}<br/>
  </p>
</t:userpage>

But it turns you like to use that user detail block in other places. So, we'll refactor it. WEB-INF/tags/userdetail.tag

<%@tag description="User Page template" pageEncoding="UTF-8"%>
<%@tag import="com.example.User" %>
<%@attribute name="user" required="true" type="com.example.User"%>

First Name: ${user.firstName} <br/>
Last Name: ${user.lastName} <br/>
Phone: ${user.phone}<br/>

Now the previous example becomes:

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>

<t:userpage userName="${user.fullName}">
  <p>
    <t:userdetail user="${user}"/>
  </p>
</t:userpage>

The beauty of JSP Tag files is that it lets you basically tag generic markup and then refactor it to your heart's content.

JSP Tag Files have pretty much usurped things like Tiles etc., at least for me. I find them much easier to use as the only structure is what you give it, nothing preconceived. Plus you can use JSP tag files for other things (like the user detail fragment above).

Here's an example that is similar to DisplayTag that I've done, but this is all done with Tag Files (and the Stripes framework, that's the s: tags..). This results in a table of rows, alternating colors, page navigation, etc:

<t:table items="${actionBean.customerList}" var="obj" css_class="display">
  <t:col css_class="checkboxcol">
    <s:checkbox name="customerIds" value="${obj.customerId}"
                onclick="handleCheckboxRangeSelection(this, event);"/>
  </t:col>
  <t:col name="customerId" title="ID"/>
  <t:col name="firstName" title="First Name"/>
  <t:col name="lastName" title="Last Name"/>
  <t:col>
    <s:link href="/Customer.action" event="preEdit">
      Edit
      <s:param name="customer.customerId" value="${obj.customerId}"/>
      <s:param name="page" value="${actionBean.page}"/>
    </s:link>
  </t:col>
</t:table>

Of course the tags work with the JSTL tags (like c:if, etc.). The only thing you can't do within the body of a tag file tag is add Java scriptlet code, but this isn't as much of a limitation as you might think. If I need scriptlet stuff, I just put the logic in to a tag and drop the tag in. Easy.

So, tag files can be pretty much whatever you want them to be. At the most basic level, it's simple cut and paste refactoring. Grab a chunk of layout, cut it out, do some simple parameterization, and replace it with a tag invocation.

At a higher level, you can do sophisticated things like this table tag I have here.

Community
  • 1
  • 1
Will Hartung
  • 115,893
  • 19
  • 128
  • 203
  • 38
    Thanks for this. It's the best tutorial I could find on JSP tag files, which were great for me coming from JSF. Wish I could give more than one up vote. – digitaljoel Nov 01 '10 at 22:04
  • 68
    +40million. Thank you for explaining it 50,000 times better than any crappy tutorial I've found. Coming from the Rails world and missing ERB, this is exactly what I need. You should write a blog. – cbmeeks Apr 08 '11 at 13:35
  • 3
    Really nice tutorial. Could you share with us the code for the table tag that you've made? I created one myself a while ago but your approach is better. – Thiago Duarte Apr 27 '12 at 21:28
  • 1
    Very useful tutorial, thanks a bunch! A little question though: when you say "If I need scriptlet stuff, I just put the logic in to a tag and drop the tag in.", you actually mean *taglib* right? Because otherwise you seem to say that scriptlet code can't be used in a tag, but a workaround is to add it to a another tag instead! – electrotype Jul 12 '12 at 23:33
  • 4
    If you create a tag file tag, the contents of that tag in the JSP file can not have scriptlet code: no scriptlet code here . But within the tag file implementing the tag itself, that can have all the scriptlet code you want, like any JSP. – Will Hartung Jul 12 '12 at 23:39
  • 1
    @WillHartung thanks for your great answer first, I have one question: how to deal with nested tag in jsp tag files? For example, in your `t:table` tag, the `t:col` must in one `table`, so how to get parent tag when implement a child tag? – donnior Aug 07 '12 at 04:11
  • 1
    I just stuff things in to known places within the request, etc. For example the `t:table` tag can create a list in `_tableColumns` than the `t:col` tag populates. Then when `t:table` performs `jsp:doBody`, the `t:col` add stuff to that list, and at the end, after the `jsp:doBody`, the `t:table` tag renders the things in the list, then removes it from the request. – Will Hartung Aug 07 '12 at 06:57
  • @WillHartung thanks, I understood your means, it's really simple. – donnior Aug 08 '12 at 05:56
  • 4
    Note - it seems the order of tags is important; jsp:attribute must come before jsp:body or you'll get an error. Also I had to set a corresponding @attribute tag to match jsp:invoke to avoid another error. Using GlassFish 3.2.2 – Ryan Apr 04 '13 at 17:15
  • tag files give really poor performance, and very little of the functionality compared to tiles-3 (but they are great if you don't need the performance nor the extra features, and there's nothing stopping you from using both – each to suit their strengths). – mck Jul 28 '13 at 18:37
  • @mck can you elaborate on your performance claims? I'm currently evaluating whether to go down the Apache Tiles road or JSP 2.x tag files, and from this answer, it seems tag files makes things alot easier, compared to maintaining the tiles.xml file(s). – Morten Jacobsen Sep 20 '13 at 10:43
  • So, in ERB, you're able to define a page template for default rendering, which uses <%= yield %>. Trying to wrap my head around how to do something similar in JSP tags. – Kevin Suttle Feb 18 '14 at 15:38
  • 2
    Just a note: Obviously you have to put all ````jsp:attribute```` before the ````jsp:body````. If I do it the other way around, a random error as described in the linked question appears. http://stackoverflow.com/questions/30219719/randomly-getting-unterminated-tag-error – Niklas Peter Dec 24 '15 at 08:41
  • 1
    Is it possible to us attributes in append manner? For example I want to add javascript code through the code in multiple places and render it at the end of the base layout – Glapa Jun 10 '17 at 10:27
  • I'm i correct that we cannot use <% scriptlet directive in the "yield" body content like we do in blade with laravel? @KevinSuttle we're talking of the same thing. did you ever found a way to do it? – exSnake May 08 '21 at 22:45
  • @WillHartung I am interested in the DisplayTag alternative in your example, since I am searching for a (more or less) drop-in replacement. Although your example is about 10 years ago, do you think you can find and share your tag files somehow? – Arthur Borsboom Jan 18 '23 at 15:36
20

I made quite easy, Django style JSP Template inheritance tag library. https://github.com/kwon37xi/jsp-template-inheritance

I think it make easy to manage layouts without learning curve.

example code :

base.jsp : layout

<%@page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://kwonnam.pe.kr/jsp/template-inheritance" prefix="layout"%>
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>JSP Template Inheritance</title>
    </head>

<h1>Head</h1>
<div>
    <layout:block name="header">
        header
    </layout:block>
</div>

<h1>Contents</h1>
<div>
    <p>
    <layout:block name="contents">
        <h2>Contents will be placed under this h2</h2>
    </layout:block>
    </p>
</div>

<div class="footer">
    <hr />
    <a href="https://github.com/kwon37xi/jsp-template-inheritance">jsp template inheritance example</a>
</div>
</html>

view.jsp : contents

<%@page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="http://kwonnam.pe.kr/jsp/template-inheritance" prefix="layout"%>
<layout:extends name="base.jsp">
    <layout:put name="header" type="REPLACE">
        <h2>This is an example about layout management with JSP Template Inheritance</h2>
    </layout:put>
    <layout:put name="contents">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin porta,
        augue ut ornare sagittis, diam libero facilisis augue, quis accumsan enim velit a mauris.
    </layout:put>
</layout:extends>
KwonNam
  • 686
  • 1
  • 8
  • 19
10

Based on the same basic idea as in @Will Hartung's answer, here is my magic one-tag extensible template engine. It even includes documentation and an example :-)

WEB-INF/tags/block.tag:

<%--
    The block tag implements a basic but useful extensible template system.

    A base template consists of a block tag without a 'template' attribute.
    The template body is specified in a standard jsp:body tag, which can
    contain EL, JSTL tags, nested block tags and other custom tags, but
    cannot contain scriptlets (scriptlets are allowed in the template file,
    but only outside of the body and attribute tags). Templates can be
    full-page templates, or smaller blocks of markup included within a page.

    The template is customizable by referencing named attributes within
    the body (via EL). Attribute values can then be set either as attributes
    of the block tag element itself (convenient for short values), or by
    using nested jsp:attribute elements (better for entire blocks of markup).

    Rendering a template block or extending it in a child template is then
    just a matter of invoking the block tag with the 'template' attribute set
    to the desired template name, and overriding template-specific attributes
    as necessary to customize it.

    Attribute values set when rendering a tag override those set in the template
    definition, which override those set in its parent template definition, etc.
    The attributes that are set in the base template are thus effectively used
    as defaults. Attributes that are not set anywhere are treated as empty.

    Internally, attributes are passed from child to parent via request-scope
    attributes, which are removed when rendering is complete.

    Here's a contrived example:

    ====== WEB-INF/tags/block.tag (the template engine tag)

    <the file you're looking at right now>

    ====== WEB-INF/templates/base.jsp (base template)

    <%@ page trimDirectiveWhitespaces="true" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    <t:block>
        <jsp:attribute name="title">Template Page</jsp:attribute>
        <jsp:attribute name="style">
            .footer { font-size: smaller; color: #aaa; }
            .content { margin: 2em; color: #009; }
            ${moreStyle}
        </jsp:attribute>
        <jsp:attribute name="footer">
            <div class="footer">
                Powered by the block tag
            </div>
        </jsp:attribute>
        <jsp:body>
            <html>
                <head>
                    <title>${title}</title>
                    <style>
                        ${style}
                    </style>
                </head>
                <body>
                    <h1>${title}</h1>
                    <div class="content">
                        ${content}
                    </div>
                    ${footer}
                </body>
            </html>
        </jsp:body>
    </t:block>

    ====== WEB-INF/templates/history.jsp (child template)

    <%@ page trimDirectiveWhitespaces="true" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    <t:block template="base" title="History Lesson">
        <jsp:attribute name="content" trim="false">
            <p>${shooter} shot first!</p>
        </jsp:attribute>
    </t:block>

    ====== history-1977.jsp (a page using child template)

    <%@ page trimDirectiveWhitespaces="true" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    <t:block template="history" shooter="Han" />

    ====== history-1997.jsp (a page using child template)

    <%@ page trimDirectiveWhitespaces="true" %>
    <%@ taglib prefix="t" tagdir="/WEB-INF/tags" %>
    <t:block template="history" title="Revised History Lesson">
        <jsp:attribute name="moreStyle">.revised { font-style: italic; }</jsp:attribute>
        <jsp:attribute name="shooter"><span class="revised">Greedo</span></jsp:attribute>
    </t:block>

--%>

<%@ tag trimDirectiveWhitespaces="true" %>
<%@ tag import="java.util.HashSet, java.util.Map, java.util.Map.Entry" %>
<%@ tag dynamic-attributes="dynattributes" %>
<%@ attribute name="template" %>
<%
    // get template name (adding default .jsp extension if it does not contain
    // any '.', and /WEB-INF/templates/ prefix if it does not start with a '/')
    String template = (String)jspContext.getAttribute("template");
    if (template != null) {
        if (!template.contains("."))
            template += ".jsp";
        if (!template.startsWith("/"))
            template = "/WEB-INF/templates/" + template;
    }
    // copy dynamic attributes into request scope so they can be accessed from included template page
    // (child is processed before parent template, so only set previously undefined attributes)
    Map<String, String> dynattributes = (Map<String, String>)jspContext.getAttribute("dynattributes");
    HashSet<String> addedAttributes = new HashSet<String>();
    for (Map.Entry<String, String> e : dynattributes.entrySet()) {
        if (jspContext.getAttribute(e.getKey(), PageContext.REQUEST_SCOPE) == null) {
            jspContext.setAttribute(e.getKey(), e.getValue(), PageContext.REQUEST_SCOPE);
            addedAttributes.add(e.getKey());
        }
    }
%>

<% if (template == null) { // this is the base template itself, so render it %>
    <jsp:doBody/>
<% } else { // this is a page using the template, so include the template instead %>
    <jsp:include page="<%= template %>" />
<% } %>

<%
    // clean up the added attributes to prevent side effect outside the current tag
    for (String key : addedAttributes) {
        jspContext.removeAttribute(key, PageContext.REQUEST_SCOPE);
    }
%>
Community
  • 1
  • 1
amichair
  • 4,073
  • 1
  • 21
  • 19
5

Use tiles. It saved my life.

But if you can't, there's the include tag, making it similar to php.

The body tag might not actually do what you need it to, unless you have super simple content. The body tag is used to define the body of a specified element. Take a look at this example:

<jsp:element name="${content.headerName}"   
   xmlns:jsp="http://java.sun.com/JSP/Page">    
   <jsp:attribute name="lang">${content.lang}</jsp:attribute>   
   <jsp:body>${content.body}</jsp:body> 
</jsp:element>

You specify the element name, any attributes that element might have ("lang" in this case), and then the text that goes in it--the body. So if

  • content.headerName = h1,
  • content.lang = fr, and
  • content.body = Heading in French

Then the output would be

<h1 lang="fr">Heading in French</h1>
geowa4
  • 40,390
  • 17
  • 88
  • 107
0

Add dependecies for use <%@tag description="User Page template" pageEncoding="UTF-8"%>

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp.jstl</groupId>
        <artifactId>javax.servlet.jsp.jstl-api</artifactId>
        <version>1.2.1</version>
    </dependency>
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
</dependencies>
Fildor
  • 14,510
  • 4
  • 35
  • 67
-1

I know this answer is coming years after the fact and there is already a great JSP answer by Will Hartung, but there is Facelets, they are even mentioned in the answers from the linked question in the original question.

Facelets SO tag description

Facelets is an XML-based view technology for the JavaServer Faces framework. Designed specifically for JSF, Facelets is intended to be a simpler and more powerful alternative to JSP-based views. Initially a separate project, the technology was standardized as part of JSF 2.0 and Java-EE 6 and has deprecated JSP. Almost all JSF 2.0 targeted component libraries do not support JSP anymore, but only Facelets.

Sadly the best plain tutorial description I found was on Wikipedia and not a tutorial site. In fact the section describing templates even does along the lines of what the original question was asking for.

Due to the fact that Java-EE 6 has deprecated JSP I would recommend going with Facelets despite the fact that it looks like there might be more required for little to no gain over JSP.

Fering
  • 322
  • 2
  • 18
  • Java EE 6 has not deprecated JSP, just deprecated using JSP as the view technology for JSF. – Ryan May 14 '19 at 13:52
  • @Ryan Since in this case both were talking about the view technology, what is wrong about having said it deprecated it? – Fering May 14 '19 at 14:04
  • 1
    The question has nothing to do with JSF. It is about pure JSP. Your answer is to use Facelets, which is for JSF. – Ryan May 14 '19 at 14:06