Custom JSP Tags

30 April 2016

We are writing a new application and the team agreed to use JSP as view technology with Freemarker for email templates. To type less on our views, I decided to write custom JSP tags.

There are two approaches in making your custom JSP tag. One is by using a Java class extending one of the classes inside javax.servlet.jsp.tagext and then writing your tld Tag Library Descriptor which calls the class and defines other attributes needed by the custom tag. This is the neat way if you're a Java purist but harder to test since you need a mock servlet implementation with mock JSP writer to do your assertions.

The other approach is by writing a tag file that goes to WEB-INF/tags folder by convention. The tag file is nothing but JSP code fragments with declared attributes of the required parameters for the tag to be able to perform its sole purpose. This is well-documented here JSP Tags 5.

input.tag

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ tag description="Bootstrap input tag with error, icon and i8n support"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>

<%@ attribute name="path" required="true" type="java.lang.String"%>
<%@ attribute name="cssClass" required="false" type="java.lang.String"%>
<%@ attribute name="cssStyle" required="false" type="java.lang.String"%>
<%@ attribute name="label" required="false" type="java.lang.String"%>
<%@ attribute name="placeholder" required="false" type="java.lang.String"%>
<%@ attribute name="required" required="false" type="java.lang.Boolean"%>
<%@ attribute name="append" required="false" type="java.lang.Boolean"%>
<%@ attribute name="prepend" required="false" type="java.lang.Boolean"%>
<%@ attribute name="icon" required="false" type="java.lang.String"%>

<c:if test="${ empty label }">
    <c:set var="label" value="${ fn:toUpperCase(fn:substring(path, 0, 1)) }${ fn:toLowerCase(fn:substring(path, 1,fn:length(path))) }" />
</c:if>
<c:if test="${ fn:length(icon) gt 1 }">
  	<c:set var="icon" value='<i class="${ icon }"></i>'/>
</c:if>

<s:bind path="${ path }">
	<div class="row form-group ${ status.error ? 'error' : '' }">
		<label class="col-sm-2 control-label" for="${ path }">
			<s:message code="${ label }"/>
			<c:if test="${ required }">
				<span class="required"> *</span>
			</c:if>
		</label>
		<div class="col-sm-4">
		  	<c:choose>
				<c:when test="${ prepend }">
					<div class="input-prepend">
		  				<span class="add-on">${ icon }</span>
						<form:input path="${ path }" cssClass="${ empty cssClass ? 'input-xlarge' : cssClass }"/>
					</div>
				</c:when>
				<c:when test="${ append }">
					<div class="input-append">
				  		<form:input path="${ path }" cssClass="${ empty cssClass ? 'input-xlarge' : cssClass }"/>
						<span class="add-on">${ icon }</span>
					</div>
				</c:when>
				<c:otherwise>
					<form:input path="${ path }" class="form-control" placeholder="${ empty placeholder ? label : placeholder }" autocomplete="off" cssStyle="${ empty cssStyle ? '' : cssStyle }"/>
				</c:otherwise>
			</c:choose>
			<c:if test="${ status.error }">
				<form:errors path="${ path }" cssClass="text-danger" />
			</c:if>
	  	</div>
	</div>
</s:bind>

calendar.tag

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ tag description="Bootstrap input grouped tag with error, calendar icon and i8n support"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>

<%@ attribute name="label" required="false" type="java.lang.String"%>
<%@ attribute name="path" required="true" type="java.lang.String"%>
<%@ attribute name="required" required="false" type="java.lang.Boolean"%>
<%@ attribute name="groupId" required="false" type="java.lang.String"%>
<%@ attribute name="btnTitle" required="false" type="java.lang.String"%>
<%@ attribute name="icon" required="false" type="java.lang.String"%>

<c:if test="${ empty label }">
    <c:set var="label" value="${ fn:toUpperCase(fn:substring(path, 0, 1)) }${ fn:toLowerCase(fn:substring(path, 1,fn:length(path))) }" />
</c:if>

<s:bind path="${ path }">
	<div class="row form-group ${ status.error ? 'error' : '' }">
		<label class="col-sm-2 control-label" for="${ path }">
			<s:message code="${ label }"/>
			<c:if test="${ required }">
				<span class="required"> *</span>
			</c:if>
		</label>
		<div class="col-sm-4">
			<div class="input-group date" id="${ empty groupId ? '' : groupId }">
				<span class="input-group-addon btn btn-sm btn-primary">
					<span class="${ icon }" title="${ empty btnTitle ? '' : btnTitle }"></span>
				</span>
				<form:input path="${ path }" class="form-control" autocomplete="off" cssStyle="width:180px;" />
			</div>
		</div>
	</div>
</s:bind>		

textarea.tag

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ tag description="Bootstrap textarea tag with error, icon and i8n support"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>

<%@ attribute name="label" required="false" type="java.lang.String"%>
<%@ attribute name="path" required="true" type="java.lang.String"%>
<%@ attribute name="required" required="false" type="java.lang.Boolean"%>

<c:if test="${ empty label }">
    <c:set var="label" value="${ fn:toUpperCase(fn:substring(path, 0, 1)) }${ fn:toLowerCase(fn:substring(path, 1,fn:length(path))) }" />
</c:if>

<s:bind path="${ path }">
	<div class="row form-group ${ status.error ? 'error' : '' }">
		<label class="col-sm-2 control-label" for="${ path }">
			<s:message code="${ label }"/>
			<c:if test="${ required }">
				<span class="required"> *</span>
			</c:if>
		</label>
		<div class="col-sm-4">
			<form:textarea path="${ path }" class="form-control" placeholder="${ empty placeholder ? label : placeholder }" />
			<c:if test="${ status.error }">
				<form:errors path="${ path }" cssClass="text-danger" />
			</c:if>
		</div>
	</div>
</s:bind>

actions.tag

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ tag	description="Bootstrap cancel, reset and submit buttons without i8n support"%>
<%@ attribute name="cancelPath" required="true" type="java.lang.String"%>

<div class="row form-group">
	<div class="col-sm-4 col-sm-offset-2">
		<a href="${ cancelPath }" class="btn btn-default">Cancel</a>
		<input type="reset" id="reset" value="Reset" class="btn btn-default" />
		<input type="submit" value="Submit" class="btn btn-primary" />
	</div>
</div>

Importing the tags

<%@taglib tagdir="/WEB-INF/tags" prefix="bs"%>