solo - validadores java
Validación de campo cruzado JSF a través de postValidate sin buscar componentes por nombre en el bean de respaldo (3)
El enfoque f:event postValidate
no te deja con muchas opciones.
La opción que prefiero es hacer las validaciones en el último componente del formulario y luego pasar los otros componentes usando el enlace y f: atributo.
Por ejemplo
<h:inputText id="field1" binding="#{field1}" ... />
<h:inputText id="field2" validator="#{...}">
<f:attribute name="field1" value="#{field1}"/>
</h:inputText>
Luego, en su validador puede tomar los otros componentes de la UIInput:
UIComponent field1 = field2.getAttributes().get("field1")
Estoy construyendo un componente compuesto de formulario de inicio de sesión. La página que lo usa pasará un controlador de eventos que validará el nombre de usuario y la contraseña. Usualmente (sin usar componentes compuestos) cuando realizamos validación de campo cruzado a través de postValidate
, el manejador de eventos tiene que buscar los componentes de los campos por su nombre. Sería preferible que el validador no hiciera esto, porque estos son detalles internos del componente que deberían abstraerse.
¿Alguna idea de cómo podría obtener los valores convertidos de los campos de nombre de usuario y contraseña en un controlador postValidate
sin conocer los detalles internos del componente compuesto?
Actualización: El objetivo de esto no es evitar buscar componentes por nombre en absoluto, sino poder validar los campos del componente compuesto de una manera que no requiera que la página y / o el bean conozcan el interior. detalles del componente.
Esto puede hacerse. En el siguiente código, postValidate
especial atención al evento postValidate
en el componente compuesto y al método postValidate
en el componente de respaldo. Observe cómo resuelve el atributo MethodExpression
e invoca el método MethodExpression
-in.
Aquí está el componente compuesto:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:cc="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<!-- Login form. -->
<cc:interface componentType="com.example.LoginForm">
<cc:attribute name="emailAddress" type="java.lang.String" required="true"/>
<cc:attribute name="rememberMe" type="java.lang.Boolean" required="true"/>
<cc:attribute name="checkCredentials"
method-signature="void checkCredentials(java.lang.String,java.lang.String,java.lang.String)"
shortDescription="Parameters are clientId, username and password. If credentials are invalid, attach a FacesMessage to the component specified by clientId."
required="true"/>
<cc:attribute name="actionListener" method-signature="void actionListener()" required="true"/>
<cc:attribute name="registerOutcome" type="java.lang.String" required="true"/>
<cc:attribute name="recoverPasswordOutcome" type="java.lang.String" required="true"/>
<cc:attribute name="headerTitle" type="java.lang.String" default="Sign In"/>
<cc:attribute name="emailAddressLabel" type="java.lang.String" default="Email address:"/>
<cc:attribute name="passwordLabel" type="java.lang.String" default="Password:"/>
<cc:attribute name="rememberMeLabel" type="java.lang.String" default="Stay signed in on this machine"/>
<cc:attribute name="loginLabel" type="java.lang.String" default="Sign In"/>
<cc:attribute name="recoverPasswordLabel" type="java.lang.String" default="Forgot password?"/>
<cc:attribute name="emailAddressRequiredMessage" type="java.lang.String" default="Email address required"/>
<cc:attribute name="passwordRequiredMessage" type="java.lang.String" default="Password required"/>
<cc:attribute name="registerLabel" type="java.lang.String" default="Register"/>
</cc:interface>
<cc:implementation>
<h:outputStylesheet library="components/example/login-form" name="style.css"/>
<div id="#{cc.clientId}">
<h:form id="form">
<f:event type="postValidate" listener="#{cc.postValidate}"/>
<div style="margin-top:10px;">
<p:panel header="#{cc.attrs.headerTitle}" styleClass="loginPanel">
<div class="login-form_errorContainer">
<p:messages rendered="#{facesContext.maximumSeverity.ordinal ge 2}"/>
</div>
<h:panelGrid columns="3">
<h:outputText styleClass="login-form_label" value="#{cc.attrs.emailAddressLabel}"/>
<h:panelGroup styleClass="login-form_cell">
<h:inputText id="emailAddress"
value="#{cc.attrs.emailAddress}"
required="true"
requiredMessage="#{cc.attrs.emailAddressRequiredMessage}"
styleClass="login-form_field"
immediate="true"/>
</h:panelGroup>
<h:panelGroup/>
<h:outputText styleClass="login-form_label" value="#{cc.attrs.passwordLabel}"/>
<h:panelGroup styleClass="login-form_cell">
<h:inputSecret id="password"
value="#{cc.attrs.password}"
required="true"
requiredMessage="#{cc.attrs.passwordRequiredMessage}"
styleClass="login-form_field"
immediate="true"/>
</h:panelGroup>
<h:link styleClass="login-form_link" value="#{cc.attrs.recoverPasswordLabel}" outcome="#{cc.attrs.recoverPasswordOutcome}"/>
<h:panelGroup/>
<p:selectBooleanCheckbox value="#{cc.attrs.rememberMe}" itemLabel="#{cc.attrs.rememberMeLabel}" immediate="true"/>
<h:panelGroup/>
<h:panelGroup/>
<h:panelGroup>
<p:commandButton id="submitForm" value="#{cc.attrs.loginLabel}" actionListener="#{cc.attrs.actionListener}" update="form"/>
<span class="login-form_or">or</span>
<h:link styleClass="login-form_link" value="#{cc.attrs.registerLabel}" outcome="#{cc.attrs.registerOutcome}"/>
</h:panelGroup>
<h:panelGroup/>
</h:panelGrid>
</p:panel>
</div>
</h:form>
</div>
</cc:implementation>
</html>
El componente de respaldo:
@FacesComponent("com.example.LoginForm")
public class LoginFormComponent extends UIInput implements NamingContainer
{
@Override
protected Object getConvertedValue(FacesContext context, Object newSubmittedValue) throws ConverterException
{
UIInput emailAddressComponent = (UIInput) findComponent(EMAIL_ADDRESS_ID);
UIInput passwordComponent = (UIInput) findComponent(PASSWORD_ID);
String emailAddress = (String) emailAddressComponent.getValue();
String password = (String) passwordComponent.getValue();
return new LoginFormValue(emailAddress, password);
}
public void postValidate(ComponentSystemEvent e) {
FacesContext ctx = getFacesContext();
// Don''t validate credentials if the username and/or password fields are invalid.
if (!ctx.getMessageList(EMAIL_ADDRESS_ID).isEmpty() || !ctx.getMessageList(PASSWORD_ID).isEmpty())
{
return;
}
LoginFormValue value = (LoginFormValue) getConvertedValue(null, null);
MethodExpression checkCredentials = (MethodExpression) getAttributes().get(CHECK_CREDENTIALS_ATTRIBUTE_NAME);
checkCredentials.invoke(ctx.getELContext(), new Object[]{getClientId(), value.getEmailAddress(), value.getPassword()});
}
@Override
public String getFamily()
{
return "javax.faces.NamingContainer";
}
public static final String CHECK_CREDENTIALS_ATTRIBUTE_NAME = "checkCredentials";
public static final String EMAIL_ADDRESS_ID = "form:emailAddress";
public static final String PASSWORD_ID = "form:password";
}
La clase LoginFormValue
para completar:
public class LoginFormValue
{
public LoginFormValue(String emailAddress, String password)
{
this.emailAddress = emailAddress;
this.password = password;
}
public String getEmailAddress()
{
return emailAddress;
}
public String getPassword()
{
return password;
}
private String emailAddress;
private String password;
}
La página que usa el formulario de inicio de sesión:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ex="http://java.sun.com/jsf/composite/components/example">
<h:head>
<title></title>
</h:head>
<h:body>
<ui:composition template="/WEB-INF/templates/myLayout.xhtml">
<ui:define name="windowTitle">Sign In</ui:define>
<ui:define name="body">
<ex:login-form emailAddress="#{loginBean.emailAddress}"
rememberMe="#{loginBean.rememberMe}"
checkCredentials="#{loginBean.checkCredentials}"
actionListener="#{loginBean.submit()}"
recoverPasswordOutcome="recover-password"
registerOutcome="signup"/>
</ui:define>
</ui:composition>
</h:body>
</html>
Y finalmente, el bean de respaldo de la página:
@Named
@RequestScoped
public class LoginBean implements Serializable
{
public String getEmailAddress()
{
return emailAddress;
}
public void setEmailAddress(String emailAddress)
{
this.emailAddress = emailAddress;
}
public boolean isRememberMe()
{
return rememberMe;
}
public void setRememberMe(boolean rememberMe)
{
this.rememberMe = rememberMe;
}
/** Action listener for login-form. Called after validation passes. */
public void submit()
{
User user = userDao.findByEmailAddress(emailAddress);
userRequestBean.login(user.getUserId());
// Remember me
if (!rememberMe)
{
return;
}
// Handle rememberMe here (create a cookie, etc.)
}
/** Called by the backing component''s postValidate event handler */
public void checkCredentials(String clientId, String emailAddress, String password)
{
if (!securityEjb.checkCredentials(emailAddress, password))
{
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Incorrect email address/password", null);
FacesContext ctx = FacesContext.getCurrentInstance();
ctx.addMessage(clientId, message);
ctx.renderResponse();
}
}
private String emailAddress = "";
private boolean rememberMe = true;
@Inject
private UserRequestBean userRequestBean;
@EJB
private SecurityEjb securityEjb;
@EJB
private UserDao userDao;
@EJB
private LoginCookieDao loginCookieDao;
}
He usado JsfWarn para un problema similar, creo que resuelve tu problema de una manera mucho más limpia.
A diferencia de los validadores JSF, los WarningValidators se realizan antes de la respuesta de representación después de que el modelo se haya actualizado, se ha invocado la aplicación, por lo que simplemente puede acceder a su aplicación para obtener resultados de validación.
@Named
public class BarWarningValidator implements WarningValidator{
@Inject
private MyValidationBean validationBean;
@Override
public void process(FacesContext context, UIInput component, ValidationResult validationResult) {
if(!validationBean.isBarValid()) {
validationResult.setFacesMessage(new FacesMessage(FacesMessage.SEVERITY_WARN, "FooBar", "This is a warning."));
}
}
}
Y agrega el validador al campo de destino:
<h:outputLabel for="bar" value="Default warning:" />
<h:inputText id="bar">
<jw:warning validator="#{barWarningValidator}" />
<f:ajax event="change" render="..." />
</h:inputText>
<h:message for="bar" />