Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Struts Wiki" for change 
notification.

The following page has been changed by MichaelJouravlev:
http://wiki.apache.org/struts/StrutsComponents

New page:
#format wiki
#language en

Struts 1.3.x (TBD) allows building portlet-like web components using JSP as 
view technology. The components work properly with or without Javascript 
enabled.

Struts Components have the following features:

   * Allows composing a page out of one or more independent components.
   * Renders a view that is always synchronized with application state.
   * Solves major issues related to stale pages and double submits.
   * Delivers input directly to a component, no central controller is needed
   * Ensures that every component renders itself independently.
   * Updates page incrementally without full page refresh if browser has 
Javascript turned on and XMLHTTPRequest is available.
   * Seamlessly integrates with Struts, enhancing a well-known action framework 
with component technology.

== Use Case: Home Page With Login Component ==

Consider the following use case: a website has a home page that should have 
different content for regular visitors one one hand, and for logged in users 
for another hand. The page contains login/logout form, a user must be presented 
with login form if he is not logged in yet. Those users who logged in must be 
able to see information about themselves and must be able to log out.

While implementing this use case the login/logout widget must reload the whole 
page if login attempt fails. Also, login/logout widget must have explicit 
knowledge about the target location, in our case it has to know the address of 
the home page. Therefore, it is nearly impossible to develop an independent 
login/logout widget that can be inserted into different pages and just work. 
Well, it is possible now. 

== Component Configuration ==

The Struts Web Component is represented with one Action class and with one (or 
more) JSP pages. Let us define the Login Component in struts-config.xml file:

{{{
<struts-config>

  <form-beans>
    <!-- Login form -->
    <form-bean name = "loginform" type="samples.login.LoginForm"/>
  </form-beans>

  <action-mappings>

      <!-- Composite page containing login component -->
      <action path="/login-struts"
              forward="/login-struts/index.jsp"/>

      <!-- Login component -->
      <action component = "Login"
              view = "/login-struts/loginComponent.jsp"
              path = "/loginintegrated"
              type = "samples.login.LoginAction"
              form = "loginform"
              scope = "session"
              validate  = "false">
          <event name="loginEvent" handler="login"/>
          <event name="logoutEvent" handler="logout"/>
      </action>

  </action-mappings>
</struts-config>
}}}

Notice new action mapping attributes and properties:

   * "component" attribute identifies an action as a component manager, such 
actions are processed differently by Action class. This name is also used in 
generated HTML for in-place update in Ajax mode.
   * "view" attribute identifies a default view for a component. Must be a JSP 
page. Often consists from several subviews, in our case the Login Component has 
two subviews "Not Logged In" and "Logged In", they will be defined in JSP file.
   * "form" is just another name for "name" property
   * "event" property allows to define request parameters as events, and 
corresponding method handlers. That is right, dispatching functions are now 
supported in the core Struts, by Action class.

== Component Action ==

The action class is deceptively simple. It handles only two events, the 
corresponding handlers are called automatically by Action class. The location 
of component's view is defined in the action mapping, so render method is not 
needed. On the other hand, most non-trivial components need to process data 
before rendering themselves or to exchange data with other components. For 
these cases you can use render" method. Its default implementation does nothing.

{{{
public class LoginAction extends Action {

    public ActionForward login (ActionMapping mapping,
                                ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response) throws Exception {

        HttpSession session = request.getSession();
        LoginForm inputForm = (LoginForm) form;

        // Log out current user first
        request.getSession().removeAttribute("USER");

        // Validation is turned off in struts-config.xml,
        // so explicitly validate user input;
        ActionMessages errors = inputForm.validate(mapping, request);

        if (errors != null) {
            saveErrors(session, errors);
        } else {
            // Use this session attribute to hold user's name
            session.setAttribute("USER", inputForm.getUsername());
        }

        // Always return null.
        return null;
    }

    public ActionForward logout (ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception 
{

        LoginForm inputForm = (LoginForm) form;

        // Clean name and password in the input/output form bean
        inputForm.setUsername(null);
        inputForm.setPassword(null);

        // Remove dialog name from session, effectively logging out
        request.getSession().removeAttribute("USER");

        // Always return null.
        return null;
    }
}
}}}

== Component Form Bean ==

Nothing exciting here, just a session-scoped form to hold user name and to 
validate credentials:

{{{
public class LoginForm extends ActionForm {

    private String username;
    public String getUsername() {return username;}
    public void setUsername(String username) {this.username = username;}

    private String password;
    public String getPassword() {return password;}
    public void setPassword(String password) {this.password = password;}

    // Generate the error messages in the same manner as usual,
    // but do not forget to turn "validate" property of the action mapping off.
    public ActionErrors validate(ActionMapping mapping, HttpServletRequest 
request) {
        if (!"guest".equalsIgnoreCase(username) ||
            !"pass".equalsIgnoreCase(password)) {
            ActionErrors errors = new ActionErrors();
            errors.add("ERROR", new ActionMessage("login.badpassword"));
            return errors;
        } else {
            return null;
        }
    }
}
}}}

== Login/Logout JSP page ==

The Login/Logout component has two subviews, both defined in one JSP page. 
Notice that content type is set to "text/xml", this is important for Ajax mode.

{{{
<%@ page contentType="text/xml;charset=UTF-8" language="java" %>

<%@ page import="java.util.ArrayList, org.apache.struts.Globals"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"; %>
<%@ taglib uri="http://struts.apache.org/tags-bean"; prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-html"; prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-logic"; prefix="logic" %>
<%@ taglib uri="http://struts.apache.org/tags-comp"; prefix="comp" %>

<comp:component>

<%-- "Not Logged In" subview --%>

<c:if test='${empty USER}'>
  <h3>Please Log In</h3>

  <!-- Displaying Struts errors -->
  <logic:messagesPresent>
    <html:messages id="error">
      <li><bean:write name="error"/></li>
    </html:messages>
  </logic:messagesPresent><br/>

  <%-- "strutsCommand" CSS class is a hook for Ajax-mode handler, it is set
       in runtime using Behaviour library. --%>

  <html:form method="get" styleClass="strutsCommand" 
action="/loginintegrated.do">
    <label for="username">Username:</label>
    <input type="text" name="username" value="${loginform.username}" 
class="datavalue"/><br/>

    <label for="password">Password:</label>
    <input type="text" name="password" value="" class="datavalue"/><br/>

    <input type="submit" name="loginEvent" value="Log In" 
class="strutsCommand"/>
  </html:form>
  <p><em>Username is "guest", password is "pass".</em></p>
</c:if>

<%-- "Logged In" subview --%>

<c:if test='${not empty USER}'>
  <h3>User Information</h3>

  <html:form method="post" styleClass="strutsCommand" 
action="/loginintegrated.do">
    <label>Current user:</label>
    <div class="datavalue">${USER}</div><br/>
    <input type="submit" name="logoutEvent" value="Log Out" 
class="strutsCommand"/><br/>
  </html:form>
</c:if>

</comp:component>
}}}

== Composite Page ==

Now we need to include the Login Component into a larger page (composite page). 
This is done with JSTL c:import tag. Do not use jsp:include, it may not work on 
some containers:

{{{
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"; %>
<%@ taglib uri="http://struts.apache.org/tags-html"; prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-comp"; prefix="comp" %>

<html>
  <body>
    <p>This paragraph is defined directly in the parent page
    and should precede the content of login control.</p>

    <%-- The login component, notice that DIV has the same ID as
         the component's name --%>
    <div id="Login">
      <c:import url="/loginintegrated.do" />
    </div>

    <p>This paragraph is defined directly in the parent page
    and should follow the content of login control.</p>
  </body>
</html>
}}}

== Done! ==

This is pretty much it. Now run the application and navigate to composite page. 
The included component will evaluate user's state and will display a login 
form. Try to log in. The submitted credentials are sent directly to a 
component, if they are not correct, the composite page is redisplayed. How? 
Behind the scenes the improved Action class as well as JSP tags work together 
to distinguish the address of a composite page during first render. This 
address is saved automatically. Then after component finishes, it reloads the 
composite page using saved address. Now you can develop independent Struts 
components!

Next installment: making components that update themselves in place without 
full page reload (Ajax mode).

Reply via email to