Recipe 9.6. Formatting Error Messages
Problem
Your application requires a custom look and feel for error messages beyond the abilities of the html:errors tag.
Solution
Use the logic:messagesPresent and the html:messages tags to display the error messages in a custom format. The JSP fragment (errors.inc.jsp) shown in Example 9-11 can be included on any page that may need to display errors.
Example 9-11. Custom error display
<logic:messagesPresent> <table border="1" bgcolor="orange" width="100%" align="center"> <tr><td> <p> <img src="/images/icon-warning.gif" border="0" vspace="2" hspace="10" align="center"> WARNING: <bean:message key="errors.heading"/> </p> <ul> <html:messages id="error"> <li><bean:write name="error"/></li> </html:messages> </ul> </td></tr> </table> <p> </logic:messagesPresent>
|
The images used in the examples are included with the online source.
|
|
Discussion
Errors displayed using the JSP code in Example 9-11 result in a display similar to Figure 9-5.
Though the html:errors tag is a convenient way of displaying error messages, it's fairly restrictive in its formatting. By default, it displays all errors for a page, starting with a header markup, then each error message, and ending with footer markup.
|
Using the property attribute, you can tell the html:errors tag to display error messages for a specific field. This ability is commonly used to display validation errors beside the invalid input field.
|
|
This tag relies on predefined messages in your MessageResources to control the look and feel of the display. These messages are shown in Table 9-1.
Table 9-1. html:errors formatting resources
Message key
|
Description
|
Sample value
|
---|
errors.header
|
Markup rendered for the header
|
<font color="red">Validation Errors<ul>
|
errors.prefix
|
Markup rendered before each error message
|
<li>
|
errors.suffix
|
Markup rendered after each error message
|
</li>
|
errors.footer
|
Markup rendered for the footer
|
</ul></font><p />
|
Though you can use these special message resources to format errors many ways, having HTML markup in a properties file spells trouble. After all, one of the goals of using an MVC framework such as Struts is to separatenot commingledata and its presentation.
The Solution presents an approach where the entire markup for presentation is contained on the JSP page. First, the logic:messagesPresent tag is used to determine if any errors will display. This tag processes its tag body if it can find a scoped variable stored under the org.apache.struts.Global.ERROR_KEY key. The tag body contains an HTML table used for formatting. A warning icon and message are displayed. Then, the html:messages tag lists each error message:
<ul> <html:messages id="error"> <li><bean:write name="error"/></li> </html:messages> </ul>
The html:messages tag doesn't render the message; rather, it acts like the logic:iterate tag. It iterates over a set of errors, creating a page-scoped variable containing the error message with a name equal to the value of the id attribute. The error message can be rendered using the bean:write or c:out tags.
So what would you do if you needed to display different types of messages? Say you wanted to display validation errors as warnings, and exceptions as more severe problems. All you need to do is store the different messages in the request using attribute names of your own choosing. Start by creating a base Action, like the one shown in Example 9-12, which provides methods that save the different errors in the servlet request. If you are using a base Action, you can add the methods shown in Example 9-12 to your own class.
Example 9-12. Base Action for saving custom error types
package com.oreilly.strutsckbk.ch09;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.Action; import org.apache.struts.action.ActionMessages;
public class BaseAction extends Action {
public static final String APP_WARNING_KEY = "APP_WARNING_KEY"; public static final String APP_ERROR_KEY = "APP_ERROR_KEY"; protected void saveAppWarnings(HttpServletRequest request, ActionMessages messages) { saveAppMessages(request, messages, APP_WARNING_KEY); }
protected void saveAppErrors(HttpServletRequest request, ActionMessages messages) { saveAppMessages(request, messages, APP_ERROR_KEY); }
private void saveAppMessages(HttpServletRequest request, ActionMessages messages, String key) { // Remove any messages attribute if none are required if ((messages == null) || messages.isEmpty( )) { request.removeAttribute(key); return; }
// Save the messages we need request.setAttribute(key, messages); } }
Actions that subclass this base Action call these specialized methods to store the messages based on severity. The custom Action shown in Example 9-13 subclasses this base Action, calling the saveAppWarnings and saveAppErrors methods as needed.
Example 9-13. Custom error handling by an Action
package com.oreilly.strutsckbk.ch09;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.PropertyUtils; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionMessage; import org.apache.struts.action.ActionMessages;
public class ValidatingLoginAction extends BaseAction {
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ActionMessages appWarnings = form.validate(mapping, request); saveAppWarnings(request, appWarnings); String username = (String) PropertyUtils.getSimpleProperty(form, "username"); String password = (String) PropertyUtils.getSimpleProperty(form, "password"); ActionMessages appErrors = new ActionMessages( ); try { SecurityService service = new SecurityService( ); service.authenticate( username, password); } catch (PasswordMatchException e) { appErrors.add("password", new ActionMessage("error. password.match")); }
saveAppErrors(request, appErrors); if (!appWarnings.isEmpty( ) || !appErrors.isEmpty( )) { return mapping.getInputForward( ); } User user = new User( ); user.setUsername(username); request.getSession( ).setAttribute("user", user); ActionMessages messages = new ActionMessages( ); ActionMessage message = new ActionMessage("message.login.success", username);
messages.add(ActionMessages.GLOBAL_MESSAGE, message);
if (!messages.isEmpty( )) { saveMessages( request.getSession(true), messages ); }
return mapping.findForward("success"); } }
If you look closely at the Action in Example 9-13, you'll see that it does something kind of strange: The ActionForm's validate method is called within the execute( ) method. Why would you do something like this? Well, remember from the requirements, you want to store validation errors as warnings; in other words, the errors should be stored under a custom request attribute name. The only way to accomplish this is for the Action to call the ActionForm's validate method. Since your Action controls the validation workflow, configure the corresponding action mapping in the struts-config.xml file so Struts doesn't call the ActionForm's validate method:
<action path="/ValidatingLogin" type="com.oreilly.strutsckbk.ch09.ValidatingLoginAction" scope="request" name="LoginForm" validate="false" input="/validating_login.jsp"> <forward name="success" path="/login_success.jsp" redirect="true"/> </action>
Now that you've got the warnings and errors being stored under separate attribute names, you can create a common JSP fragment to display them. The JSP fragment (errors2.inc.jsp) shown in Example 9-14 uses the name attribute of the logic:messagesPresent tag and html:messages tag to indicate the type of error to process.
Example 9-14. JSP fragment for displaying custom errors
<logic:messagesPresent name="APP_ERROR_KEY"> <table border="1" bgcolor="orange" width="100%" align="center"><tr><td> <p> <img src="/images/icon-alert.gif" border="0" vspace="2" hspace="10" align="center"> <bean:message key="errors.heading"/> </p> <ul> <html:messages id="error" name="APP_ERROR_KEY"> <li><bean:write name="error"/></li> </html:messages> </ul> </td></tr></table> <p> </logic:messagesPresent> <logic:messagesPresent name="APP_WARNING_KEY"> <table border="1" bgcolor="yellow" width="100%" align="center"><tr><td> <p> <img src="/images/icon-warning.gif" border="0" vspace="2" hspace="10" align="center"> <bean:message key="warnings.heading"/> </p> <ul> <html:messages id="error" name="APP_WARNING_KEY"> <li><bean:write name="error"/></li> </html:messages> </ul> </td></tr></table> <p> </logic:messagesPresent>
Now, when the errors are displayed, you'll see a page like the one shown in Figure 9-6.
If you're new to web development, this last example may seem a bit extreme, but it demonstrates the flexibility afforded by Struts. If you don't like the way Struts handles errors, then you can do it yourself. As long as you utilize the Struts APIs, you'll find that the Struts tags can cope with most customizations in an intelligent and logical way.
See Also
Ted Husted discusses custom error formatting in his invaluable set of Struts Tips. The specific tip (#17) can be found at http://husted.com/struts/tips/017.html.
Rick Reumann, a frequent poster to the struts-user mailing list and all-around good guy, prefers to call the ActionForm's validate method as he explains in the archived posting at http://marc.theaimsgroup.com/?l=struts-user&m=109242668231755&w=2.
If you are using declarative exception handling, you can use a similar approach for storing the exception error message by creating a custom exception handler. Custom exception handlers are discussed in Recipe 9.2.
|
No comments:
Post a Comment