JSP Controls Tag Library
 

Getting Started

Overview

JSP Controls library allows to create components that have the following features in any combination:

  • Pure JSP or Struts/JSP
  • Embedded or Standalone
  • Redirect-After-Post or Ajax

If you can't wait to see an embedded Ajax component, then go directly to "Ajax In-Place Update" chapter. If you of a more patient sort then keep on reading, this guide will unfold the JSP Controls concepts gradually, step by step.

To showcase JSP Controls technology we will build a simple but useful login component. As all components, the Login Component retains its state on the server between requests. The component has two states: "Logged In" and "Not Logged In", two corresponding views: "loggedin" and "notloggedin" and two input events: "Login" and "Logout".

Standalone Login Component

Standalone, pure JSP, Redirect-After-Post component

This is the simplest kind of component. We will define this component in one JSP page that will have the name loginpage.jsp. The corresponding to Login Component view occupies a whole browser window, not just a fragment of it, therefore the component definition contains proper HTML opening and closing tags.

Like any component built with JSP Controls, a standalone component handles input and output in two separate phases. JSP page defines event handlers for input phase and view renderers for output phase, as shown in the following outline:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/lib/jspcontrols.tld" prefix="jc" %>
<html>
<body>

<%-- Input (accept) phase --%>
<jc:accept>
  <jc:handler name="login">
    <%-- Process login event here --%>
  </jc:handler>
  <jc:handler name="logout">
    <%-- Process logout event here --%>
  </jc:handler>
  <jc:reload/>
</jc:accept>

<jc:render>
  <%-- Deduce from application state what view to display --%>
</jc:render>

<jc:render view="notloggedin">
  <%-- Display login panel --%>
</jc:render>

<jc:render view="loggedin">
  <%-- Display logout panel --%>
</jc:render>

</body>
</html>

The order of tags is important since JSP page is processed from top to bottom, therefore the tags defined first will be processed earlier. Input and output phases are handled by accept and render tags, respectively.

Let us assume that the Login Component has just rendered a login form, now we need to log in by submitting proper user credentials. So we fill out user name and password fields and submit the login form, initiating the input phase.

The body of accept tag is evaluated if request contains input event or if request is submitted with POST method. An event is a request parameter named like this: "login.jspcontrols.event", where "login" is the name of the handler, and ".jspcontrols.event" is a standard suffix that defines a request parameter as an event.

Accept tag contains Handler tags that process input events. Handler tags must be always defined inside the body of Accept tag or they will not be executed. The body of handler tag is evaluated if value of its event attribute corresponds to event name.

Here is what the login handler looks like:

  <jc:handler event="login">
    <%
        // Log out current user first
        session.removeAttribute("USER");

        // Read username and password submitted from login form
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // Store user name in session if needed to redisplay.
        // No need to redisplay password.
        session.setAttribute("username", username);

        // Account found
        if (authenticate(username, password)) {
            // Log in user
            session.setAttribute("USER", username);
            // No need to cache username after login
            session.removeAttribute("username");

        // User accout not found, generate error message
        } else {
            // Generate error message
            ArrayList messages = new ArrayList();
            String message = msgs.getMessage("login.accountnotfound");
            messages.add(message);

            // Store errors in the session for use at render phase
            session.setAttribute("MESSAGES", messages);
        }
    %>
  </jc:handler>

If the user provided proper account credentials, his name is saved in session-scoped variable "USER". Otherwise the "USER" variable is removed from the session. This fact will be used on render phase to verify whether a user is logged in or not. Let us put an incorrect username this time to see how the login form is reloaded.

After input data is processed by a handler, the component switches to output phase using Reload tag. This tag is the essense of Redirect-After-Post method. It separates input and output phases with redirect response, making each phase a separate HTTP request. This helps to fight with Back button issues and allows to refresh a view any time without resending input data to the server.

Note

A component is not required to render a view. After input data is processed, a component can redirect to another location instead of rendering a view.

Reload tag redirects browser to the same JSP page. Redirected request has GET type and is "clean", that is, it does not contain query parameters. Therefore, Accept tag is not evaluated this time. Instead, Render tags are evaluated, displaying an appropriate view.

Depending on how an application is written, a view name can be selected either on input phase and stored in the session, or on the render phase but before specific view renderers are evaluated.

The standard practice is to calculate a view name in the beginning of render phase. This is usually done in Render tag with no attributes. If "view" attribute is omitted from Render tag, its body is always evaluated:

<jc:render>
  <%
      pageContext.setAttribute("jspcontrolsViewSelected",
          session.getAttribute("USER") != null ? 
                               "loggedin" : "notloggedin");
  %>
</jc:render>

The name of a view is set in a session variable "jspcontrolsViewSelected". This variable is checked by render tags to figure out which view to render. In the above snippet, "notloggedin" view is selected if the user could not log in.

Render tag evaluates its body if the "view" attribute is specified and corresponds to a view, set in jspcontrolsViewSelected page variable. Thus, if user information was not entered correctly, the login form is [re]displayed. If login form is being shown after unsuccessful login attempt, it displays the username entered on prior try. The username has been saved in the session by login handler during accept phase.

<jc:render view="notloggedin">
  <h3>Log In</h3>
  <form>
    Name: <input type="text" name="username" 
                 value="<c:out value="${username}"/>"/><br/>
    Password: <input type="password" name="password" value="" />
    <input type="submit" name="login.jspcontrols.event" value="Log In" />
  </form>
</jc:render>

At this point both phases have finished. On input phase username and password had been accepted and validated, then on output phase the view was rendered. We started from the assumption that a login form has already been rendered. The view is rendered when a user navigates to the component address, because it generates a GET request with no additional query parameters. Therefore, Accept tag is not evaluated, while Render tag displays a view corresponding to current component state, which is "Not Logged In".

Because the first login attempt was not successful, let us try to log in again, now by providing proper account information. Because the form can contain more than one button, the specific event is specified in button's "name" attribute. This simple trick leaves "value" attribute for button caption. When a user submits login form, the input is processed by login event handler, this time the user information is correct, so the user logs in.

After the user logged in, the page reloads iteself and switches to output phase. Because the user has logged in, the "loggedin" view is chosen, and the Render tag shows user information form that displays user's name and allows to log out:

<jc:render view="loggedin">
  <h3>Log Out</h3>
  <form>
    Logged in as: <c:out value="${sessionScope.USER}"/><br/>
    <input type="submit" name="logout.jspcontrols.event" value="Log Out" />
  </form>
</jc:render>

The second two-phase request cycle has finished. Now whenever a logged in user navigates to loginpage.jsp location, he will be presented with logout form. Clicking on "Log Out" button fires logout event handler, and user logs out from the application:

  <jc:handler name="logout">
    <% session.removeAttribute("USER"); %>
  </jc:handler>

The next time loginpage.jsp is navigated, it shows login form again.

Full source code

Now it is time for complete Login Component example. You can copy the source code and deploy it to your servlet/JSP container to see the example in work. Don't forget to download the tag library.

You may want to use some sort of HTTP sniffer to have better understanding how Redirect-After-Post works. LiveHTTPHeaders is a great tool for the job if you use Mozilla Firefox.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.ArrayList,
                 net.jspcontrols.util.Constants"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<%@ taglib uri="http://jspcontrols.net/tags-core" prefix="jc" %>

<html>

  <body style = "border: 2px dotted #fa9010; padding: 5px;">

<%-- **********************************************************************
     The stub for authentication service; allows only "user"/"pass" account
     ********************************************************************** --%>

    <%!
      public boolean authenticate(String username, String password){
        return "user".equals(username) && "pass".equals(password);
      }
    %>

<%-- **********************************************************************
     Input phase, either postback or regular link containing an event
     ********************************************************************** --%>

    <jc:accept>

      <%-- Processing login --%>
      <jc:handler event="login">
        <%
          // Log out current user
          session.removeAttribute("USER");

          // Read username and password submitted from dialog form
          String username = request.getParameter("username");
          String password = request.getParameter("password");

          // Store user name in session if needed to redisplay.
          // No need to redisplay password, so it is not stored.
          session.setAttribute("username", username);

          // Log the user in
          if (authenticate(username, password)) {
            session.removeAttribute("username");
            session.setAttribute("USER", username);

          // User accout is not found, generate error messages
          } else {
            ArrayList messages = new ArrayList();
            String message = "Account not found.";
            messages.add(message);
            session.setAttribute("MESSAGES", messages);
          }
        %>
      </jc:handler>

      <%-- Processing logout --%>
        <jc:handler event="logout">
          <%
            // Log out current user
            session.removeAttribute("USER");
          %>
        </jc:handler>

      <%-- Accept phase has finished. Reload this page to render a view. --%>
      <jc:reload/>
    </jc:accept>

<%-- **********************************************************************
     Prerender Phase: choose view corresponding to resource state
     ********************************************************************** --%>

    <jc:prerender>
      <%
        pageContext.setAttribute(Constants.SELECTED_VIEW_NAME,
          session.getAttribute("USER") != null ? "loggedin" : "notloggedin");
      %>
    </jc:prerender>

<%-- **********************************************************************
     Common Render Phase: write out error messages and remove them from session
     ********************************************************************** --%>

    <jc:render>
      <c:if test='${not empty sessionScope.MESSAGES}'>
        <c:forEach var='message' items='${sessionScope.MESSAGES}'>
          <ul>
            <li><b><c:out value='${message}'/></b></li>
          </ul>
        </c:forEach>
        <% session.removeAttribute("MESSAGES"); %>
      </c:if>
    </jc:render>

<%-- **********************************************************************
     Render Phase: User is not logged in. Display login form.
     ********************************************************************** --%>

    <jc:render view="notloggedin">
      <h3>Log In</h3>
      <form>
        <table>

          <!-- username and password -->
          <tr>
            <td align="left">Username:</td>
            <td><input type="text"
                       name="username" maxlength="20" size="20"
                       value="<c:out value='${username}'/>" />
            </td>
          </tr>
          <tr>
            <td align="left">Password:</td>
            <td><input type="password"
                       name="password" maxlength="20" size="20"
                       value="" />
            </td>
          </tr>

          <!-- Login button -->
          <tr>
            <td></td>
            <td align="left">
              <input type="submit"
                     name="login.jspcontrols.event"
                     value="Log In" />
            </td>
          </tr>
        </table>
      </form>
      <p><em>Username is "user", password is "pass".</em></p>
    </jc:render>

<%-- **********************************************************************
     Render Phase: User is logged in. Display user info and logout form.
     ********************************************************************** --%>

    <jc:render view="loggedin">
      <form>
        <table>

          <!-- User information -->
          <tr>
            <td align="left">Username:</td>
            <td><c:out value="${sessionScope.USER}"/></td>
          </tr>

          <!-- Logout button-->
          <tr>
            <td></td>
            <td align="left">
              <input type="submit"
                     name="logout.jspcontrols.event"
                     value="Log Out" />
            </td>
          </tr>
        </table>
      </form>
    </jc:render>

<%-- **********************************************************************
     Render Phase: Clean messages so they won't display on refresh.
     ********************************************************************** --%>

    <jc:render>
      <% session.removeAttribute("MESSAGES"); %>
    </jc:render>
  </body>
</html>