Based on knowledge from the previous recipe, we will move forward and create a custom component that will be rendered by a custom renderer and will have attached a custom validator (as an exercise, try to add a custom converter as well). Our component will consist of a text field that accepts only valid e-mail addresses; therefore it will extend the UIInput
component.
We have developed this recipe with NetBeans 6.8, JSF 2.0, and GlassFish v3. The JSF 2.0 classes were obtained from the NetBeans JSF 2.0 bundled library.
To begin with, let's say that the new component will be named emailInput
and it looks as shown next (we have listed the entire JSP page):
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%> <%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%> <%@taglib prefix="e" uri="http://packt.net/cookbook/components"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <f:view> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>Writing a JSF custom component - an email component</title> </head> <body> <h:form id="emailForm"> <h:outputText value="Insert your e-mail:"/><br /> <e:emailInput value="#{myEmailBean.email}" id="emailID" /> <h:message showSummary="true" showDetail="false" for="emailID" style="color: red; text-decoration:overline"/> <h:commandButton id="submit" action="response?faces-redirect=true" value="Submit"/> </h:form> </body> </html> </f:view>
As usual, we start by developing the component class (the component class controls the server-side behavior of a JSF component). This class is listed next:
package custom.component; import javax.faces.component.UIInput; public class UIEmailInput extends UIInput { public UIEmailInput() { super(); EmailValidator emailValidator=new EmailValidator(); addValidator(emailValidator); } @Override public String getFamily() { return "EMAIL_FAMILY"; } }
As you can see, we have used the component constructor for setting the custom validator, EmailValidator
, which is listed next (for more details about writing validators refer to Chapter 2, Using Standard and Custom Validators in JSF):
package custom.component; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.FacesValidator; import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; @FacesValidator(value = "emailValidator") public class EmailValidator implements Validator { private static final String IP_REGEX = ".+@.+\.[a-z]+"; public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException { String emailAddress = (String) value; Pattern mask = null; mask = Pattern.compile(IP_REGEX); Matcher matcher = mask.matcher(emailAddress); if (!matcher.matches()) { FacesMessage message = new FacesMessage(); message.setDetail("E-mail not valid"); message.setSummary("E-mail not valid"); message.setSeverity(FacesMessage.SEVERITY_ERROR); throw new ValidatorException(message); } } }
The next task is to write a custom renderer. This class will be responsible for transforming the component into HTML and taking any form posts and passing the values from the post back to the component. We will first list the code, and then look into the details:
package custom.component; import java.io.IOException; import java.util.Map; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.component.ValueHolder; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.Renderer; public class UIEmailInputRenderer extends Renderer{ @Override public void decode(FacesContext ctx, UIComponent ui_comp) { if (ctx == null) { throw new NullPointerException("NULL CONTEXT NOT ALLOWED!"); } else if (ui_comp == null) { throw new NullPointerException("NULL COMPONENT NOT ALLOWED!"); } if (ui_comp instanceof UIInput) { UIInput uiInput = (UIInput)ui_comp; String clientId = uiInput.getClientId(ctx); Map requestMap = ctx.getExternalContext(). getRequestParameterMap(); String new_value = (String)requestMap.get(clientId); if (null != new_value) { uiInput.setSubmittedValue(new_value); } } } @Override public void encodeEnd(FacesContext ctx, UIComponent ui_comp) throws IOException { if (ctx == null) { throw new NullPointerException("NULL CONTEXT NOT ALLOWED!"); } else if (ui_comp == null) { throw new NullPointerException("NULL COMPONENT NOT ALLOWED!"); } ResponseWriter responseWriter = ctx.getResponseWriter(); responseWriter.startElement("input", ui_comp); responseWriter.writeAttribute("type", "text", "text"); String id = (String)ui_comp.getClientId(ctx); responseWriter.writeAttribute("id", id, "id"); responseWriter.writeAttribute("name", id, "id"); Object obj = getValue(ui_comp); responseWriter.writeAttribute("value", formattingValue(obj), "value"); responseWriter.endElement("input"); } private String formattingValue(Object format_value) { return format_value.toString(); } protected Object getValue(UIComponent ui_comp) { Object obj = null; if (ui_comp instanceof UIInput) { obj = ((UIInput) ui_comp).getSubmittedValue(); } if ((null == obj) && (ui_comp instanceof ValueHolder)) { obj = ((ValueHolder) ui_comp).getValue(); } return obj; } }
The first method, named decode,
takes parameters from a form post and sets the values for the component. After checking the context and component state (they can't be null
), we isolate the UIInput
components and we extract values from the request and put them as submitted values for the component.
The next method is encodeEnd
. It generates the HTML code to represent the component on the browser. For advanced components, which have a body, we should have three overridden methods (we won't repeat this again, therefore it is considered known in the following recipes):
encodeBegin
: This starts the element for the root componentencodeChildren
: This would cause all of the children to be encodedencodeEnd
: This closes the elementNow, the tag handler should indicate that we have a separate renderer class, and for this the getRendererType
method must not return null
:
public String getRendererType() { return "EMAIL_RENDERER"; }
The last thing that we must accomplish is to set the renderer in the faces-config.xml
descriptor (we need this even if we are using JSF 2.0). This can be done as shown next:
… <render-kit> <renderer> <description> Renderer for the e-mail component. </description> <component-family>EMAIL_FAMILY</component-family> <renderer-type>EMAIL_RENDERER</renderer-type> <renderer-class> custom.component.UIEmailInputRenderer </renderer-class> </renderer> </render-kit> …
That's all! Now, we have a custom component that can be used for providing valid e-mail addresses to our bean, MyEmailBean
.
If you read the introduction of this chapter it becomes easier to understand how our custom component works.