![JAR search and dependency download from the Maven repository](/logo.png)
org.jboss.seam.faces.component.UIInputContainer Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.seam.faces.component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.el.ValueReference;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIMessage;
import javax.faces.component.UINamingContainer;
import javax.faces.component.UIViewRoot;
import javax.faces.component.html.HtmlOutputLabel;
import javax.faces.context.FacesContext;
import javax.faces.validator.BeanValidator;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
* UIInputContainer is a supplemental component for a JSF 2.0
* composite component encapsulating one or more input components
* (EditableValueHolder), their corresponding message
* components (UIMessage) and a label
* (HtmlOutputLabel). This component takes care of wiring the
* label to the first input and the messages to each input in sequence. It also
* assigns two implicit attribute values, "required" and "invalid" to indicate
* that a required input field is present and whether there are any validation
* errors, respectively. To determine if a input field is required, both the
* required attribute is consulted and whether the property has Bean Validation
* constraints. Finally, if the "label" attribute is not provided on the
* composite component, the label value will be derived from the id of the
* composite component, for convenience.
*
*
* Composite component definition example (minus layout):
*
*
*
* <cc:interface componentType="org.jboss.seam.faces.InputContainer"/>
* <cc:implementation>
* <h:outputLabel id="label" value="#{cc.attrs.label}:" styleClass="#{cc.attrs.invalid ? 'invalid' : ''}">
* <h:ouputText styleClass="required" rendered="#{cc.attrs.required}" value="*"/>
* </h:outputLabel>
* <cc:insertChildren/>
* <h:message id="message" errorClass="invalid message" rendered="#{cc.attrs.invalid}"/>
* </cc:implementation>
*
*
*
* Composite component usage example:
*
*
*
* <example:inputContainer id="name">
* <h:inputText id="input" value="#{person.name}"/>
* </example:inputContainer>
*
*
*
* Possible enhancements:
*
*
* - append styleClass "invalid" to label, inputs and messages when invalid
*
*
* @author Dan Allen
*/
@FacesComponent(UIInputContainer.COMPONENT_TYPE)
public class UIInputContainer extends UIComponentBase implements NamingContainer
{
/**
* The standard component type for this component.
*/
public static final String COMPONENT_TYPE = "org.jboss.seam.faces.InputContainer";
protected static final String HTML_ID_ATTR_NAME = "id";
protected static final String HTML_CLASS_ATTR_NAME = "class";
protected static final String HTML_STYLE_ATTR_NAME = "style";
private boolean beanValidationPresent = false;
public UIInputContainer()
{
beanValidationPresent = isClassPresent("javax.validation.Validator");
}
@Override
public String getFamily()
{
return UINamingContainer.COMPONENT_FAMILY;
}
/**
* The name of the auto-generated composite component attribute that holds a
* boolean indicating whether the the template contains an invalid input.
*/
public String getInvalidAttributeName()
{
return "invalid";
}
/**
* The name of the auto-generated composite component attribute that holds a
* boolean indicating whether the template contains a required input.
*/
public String getRequiredAttributeName()
{
return "required";
}
/**
* The name of the composite component attribute that holds the string label
* for this set of inputs. If the label attribute is not provided, one will
* be generated from the id of the composite component or, if the id is
* defaulted, the name of the property bound to the first input.
*/
public String getLabelAttributeName()
{
return "label";
}
/**
* The name of the auto-generated composite component attribute that holds
* the elements in this input container. The elements include the label, a
* list of inputs and a cooresponding list of messages.
*/
public String getElementsAttributeName()
{
return "elements";
}
/**
* The name of the composite component attribute that holds a boolean
* indicating whether the component template should be enclosed in an HTML
* element, so that it be referenced from JavaScript.
*/
public String getEncloseAttributeName()
{
return "enclose";
}
public String getContainerElementName()
{
return "div";
}
public String getDefaultLabelId()
{
return "label";
}
public String getDefaultInputId()
{
return "input";
}
public String getDefaultMessageId()
{
return "message";
}
@Override
public void encodeBegin(final FacesContext context) throws IOException
{
if (!isRendered())
{
return;
}
super.encodeBegin(context);
InputContainerElements elements = scan(getFacet(UIComponent.COMPOSITE_FACET_NAME), null, context);
// assignIds(elements, context);
wire(elements, context);
getAttributes().put(getElementsAttributeName(), elements);
if (elements.hasValidationError())
{
getAttributes().put(getInvalidAttributeName(), true);
}
// set the required attribute, but only if the user didn't already assign
// it
if (!getAttributes().containsKey(getRequiredAttributeName()) && elements.hasRequiredInput())
{
getAttributes().put(getRequiredAttributeName(), true);
}
if (!getAttributes().containsKey(getLabelAttributeName()))
{
getAttributes().put(getLabelAttributeName(), generateLabel(elements, context));
}
if (Boolean.TRUE.equals(getAttributes().get(getEncloseAttributeName())))
{
startContainerElement(context);
}
}
@Override
public void encodeEnd(final FacesContext context) throws IOException
{
if (!isRendered())
{
return;
}
super.encodeEnd(context);
if (Boolean.TRUE.equals(getAttributes().get(getEncloseAttributeName())))
{
endContainerElement(context);
}
}
protected void startContainerElement(final FacesContext context) throws IOException
{
context.getResponseWriter().startElement(getContainerElementName(), this);
String style = (getAttributes().get("style") != null ? getAttributes().get("style").toString().trim() : null);
if (style.length() > 0)
{
context.getResponseWriter().writeAttribute(HTML_STYLE_ATTR_NAME, style, HTML_STYLE_ATTR_NAME);
}
String styleClass = (getAttributes().get("styleClass") != null ? getAttributes().get("styleClass").toString().trim() : null);
if (styleClass.length() > 0)
{
context.getResponseWriter().writeAttribute(HTML_CLASS_ATTR_NAME, styleClass, HTML_CLASS_ATTR_NAME);
}
context.getResponseWriter().writeAttribute(HTML_ID_ATTR_NAME, getClientId(context), HTML_ID_ATTR_NAME);
}
protected void endContainerElement(final FacesContext context) throws IOException
{
context.getResponseWriter().endElement(getContainerElementName());
}
protected String generateLabel(final InputContainerElements elements, final FacesContext context)
{
String name = getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX) ? elements.getPropertyName(context) : getId();
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
/**
* Walk the component tree branch built by the composite component and locate
* the input container elements.
*
* @return a composite object of the input container elements
*/
protected InputContainerElements scan(final UIComponent component, InputContainerElements elements, final FacesContext context)
{
if (elements == null)
{
elements = new InputContainerElements();
}
// NOTE we need to walk the tree ignoring rendered attribute because it's
// condition
// could be based on what we discover
if ((elements.getLabel() == null) && (component instanceof HtmlOutputLabel))
{
elements.setLabel((HtmlOutputLabel) component);
}
else if (component instanceof EditableValueHolder)
{
elements.registerInput((EditableValueHolder) component, getDefaultValidator(context), context);
}
else if (component instanceof UIMessage)
{
elements.registerMessage((UIMessage) component);
}
// may need to walk smarter to ensure "element of least suprise"
for (UIComponent child : component.getChildren())
{
scan(child, elements, context);
}
return elements;
}
// assigning ids seems to break form submissions, but I don't know why
public void assignIds(final InputContainerElements elements, final FacesContext context)
{
boolean refreshIds = false;
if (getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
{
setId(elements.getPropertyName(context));
refreshIds = true;
}
UIComponent label = elements.getLabel();
if (label != null)
{
if (label.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
{
label.setId(getDefaultLabelId());
}
else if (refreshIds)
{
label.setId(label.getId());
}
}
for (int i = 0, len = elements.getInputs().size(); i < len; i++)
{
UIComponent input = (UIComponent) elements.getInputs().get(i);
if (input.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
{
input.setId(getDefaultInputId() + (i == 0 ? "" : (i + 1)));
}
else if (refreshIds)
{
input.setId(input.getId());
}
}
for (int i = 0, len = elements.getMessages().size(); i < len; i++)
{
UIComponent msg = elements.getMessages().get(i);
if (msg.getId().startsWith(UIViewRoot.UNIQUE_ID_PREFIX))
{
msg.setId(getDefaultMessageId() + (i == 0 ? "" : (i + 1)));
}
else if (refreshIds)
{
msg.setId(msg.getId());
}
}
}
/**
* Wire the label and messages to the input(s)
*/
protected void wire(final InputContainerElements elements, final FacesContext context)
{
elements.wire(context);
}
/**
* Get the default Bean Validation Validator to read the contraints for a
* property.
*/
private Validator getDefaultValidator(final FacesContext context) throws FacesException
{
if (!beanValidationPresent)
{
return null;
}
ValidatorFactory validatorFactory;
Object cachedObject = context.getExternalContext().getApplicationMap().get(BeanValidator.VALIDATOR_FACTORY_KEY);
if (cachedObject instanceof ValidatorFactory)
{
validatorFactory = (ValidatorFactory) cachedObject;
}
else
{
try
{
validatorFactory = Validation.buildDefaultValidatorFactory();
}
catch (ValidationException e)
{
throw new FacesException("Could not build a default Bean Validator factory", e);
}
context.getExternalContext().getApplicationMap().put(BeanValidator.VALIDATOR_FACTORY_KEY, validatorFactory);
}
return validatorFactory.getValidator();
}
private boolean isClassPresent(final String fqcn)
{
try
{
if (Thread.currentThread().getContextClassLoader() != null)
{
return Thread.currentThread().getContextClassLoader().loadClass(fqcn) != null;
}
else
{
return Class.forName(fqcn) != null;
}
}
catch (ClassNotFoundException e)
{
return false;
}
catch (NoClassDefFoundError e)
{
return false;
}
}
public static class InputContainerElements
{
private String propertyName;
private HtmlOutputLabel label;
private final List inputs = new ArrayList();
private final List messages = new ArrayList();
private boolean validationError = false;
private boolean requiredInput = false;
public HtmlOutputLabel getLabel()
{
return label;
}
public void setLabel(final HtmlOutputLabel label)
{
this.label = label;
}
public List getInputs()
{
return inputs;
}
public void registerInput(final EditableValueHolder input, final Validator validator, final FacesContext context)
{
inputs.add(input);
if (input.isRequired() || isRequiredByConstraint(input, validator, context))
{
requiredInput = true;
}
if (!input.isValid())
{
validationError = true;
}
// optimization to avoid loop if already flagged
else if (!validationError)
{
Iterator it = context.getMessages(((UIComponent) input).getClientId(context));
while (it.hasNext())
{
if (it.next().getSeverity().compareTo(FacesMessage.SEVERITY_WARN) >= 0)
{
validationError = true;
break;
}
}
}
}
public List getMessages()
{
return messages;
}
public void registerMessage(final UIMessage message)
{
messages.add(message);
}
public boolean hasValidationError()
{
return validationError;
}
public boolean hasRequiredInput()
{
return requiredInput;
}
private boolean isRequiredByConstraint(final EditableValueHolder input, final Validator validator, final FacesContext context)
{
if (validator == null)
{
return false;
}
// NOTE believe it or not, getValueReference on ValueExpression is
// broken, so we have to do it ourselves
ValueReference vref = new ValueExpressionAnalyzer(((UIComponent) input).getValueExpression("value")).getValueReference(context.getELContext());
return validator.getConstraintsForClass(vref.getBase().getClass()).getConstraintsForProperty((String) vref.getProperty()).hasConstraints();
}
public String getPropertyName(final FacesContext context)
{
if (propertyName != null)
{
return propertyName;
}
if (inputs.size() == 0)
{
return null;
}
propertyName = (String) new ValueExpressionAnalyzer(((UIComponent) inputs.get(0)).getValueExpression("value")).getValueReference(context.getELContext()).getProperty();
return propertyName;
}
public void wire(final FacesContext context)
{
int numInputs = inputs.size();
if (numInputs > 0)
{
if (label != null)
{
label.setFor(((UIComponent) inputs.get(0)).getClientId(context));
}
for (int i = 0, len = messages.size(); i < len; i++)
{
if (i < numInputs)
{
messages.get(i).setFor(((UIComponent) inputs.get(i)).getClientId(context));
}
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy