From cd5de46bba5fe53ff893246233577b22fe87dc90 Mon Sep 17 00:00:00 2001 From: gonzalad Date: Tue, 24 Jul 2012 15:04:21 +0200 Subject: [PATCH] UIInputContainer assigns label attribute for any child input component Improves default validation JSF message (i.e. prepends label value instead of component id to validation message). Label attribute is assigned only if not explicitly set. --- .../faces/component/UIInputContainer.java | 920 +++++++++--------- 1 file changed, 477 insertions(+), 443 deletions(-) diff --git a/api/src/main/java/org/jboss/seam/faces/component/UIInputContainer.java b/api/src/main/java/org/jboss/seam/faces/component/UIInputContainer.java index ae39b43..f9518b3 100644 --- a/api/src/main/java/org/jboss/seam/faces/component/UIInputContainer.java +++ b/api/src/main/java/org/jboss/seam/faces/component/UIInputContainer.java @@ -1,444 +1,478 @@ -/* - * JBoss, Home of Professional Open Source - * Copyright 2011, Red Hat, Inc., and individual contributors - * by the @authors tag. See the copyright.txt in the distribution for a - * full listing of individual contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jboss.seam.faces.component; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -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. 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: - *

- * - *

- *

- * NOTE: Firefox does not properly associate a label with the target input if the input id contains a colon (:), the default - * separator character in JSF. JSF 2 allows developers to set the value via an initialization parameter (context-param in - * web.xml) keyed to javax.faces.SEPARATOR_CHAR. We recommend that you override this setting to make the separator an underscore - * (_). - *

- * - * @author Dan Allen - * @author Jose Rodolfo freitas - */ -@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); - } - - getAttributes().put(getRequiredAttributeName(), elements.hasRequiredInput()); - - /* - * for some reason, Mojarra is not filling Attribute Map with "label" key if label attr has an EL value, so I added a - * labelHasEmptyValue to guarantee that there was no label setted. - */ - if (getValueExpression(getLabelAttributeName()) == null - && (!getAttributes().containsKey(getLabelAttributeName()) || labelHasEmptyValue(elements))) { - 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; - } - } - - private boolean labelHasEmptyValue(InputContainerElements elements) { - if (elements.getLabel() == null || elements.getLabel().getValue() == null) - return false; - return (elements.getLabel().getValue().toString().trim().equals(":") || elements.getLabel().getValue().toString() - .trim().equals("")); - } - - 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; - - 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.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() { - //We have to scan these each time as the value could change in an AJAX request - for (EditableValueHolder holder : inputs) { - if (holder.isRequired()) { - return true; - } - } - return false; - } - - 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)); - } - } - } - } - } +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc., and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.ValueExpression; +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. 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
  • + *
+ *

+ *

+ * NOTE: Firefox does not properly associate a label with the target input if the input id contains a colon (:), the default + * separator character in JSF. JSF 2 allows developers to set the value via an initialization parameter (context-param in + * web.xml) keyed to javax.faces.SEPARATOR_CHAR. We recommend that you override this setting to make the separator an underscore + * (_). + *

+ * + * @author Dan Allen + * @author Jose Rodolfo freitas + */ +@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); + + /* + * for some reason, Mojarra is not filling Attribute Map with "label" key if label attr has an EL value, so I added a + * labelHasEmptyValue to guarantee that there was no label setted. + */ + if (getValueExpression(getLabelAttributeName()) == null + && (!getAttributes().containsKey(getLabelAttributeName()) || labelHasEmptyValue(elements))) { + getAttributes().put(getLabelAttributeName(), generateLabel(elements, context)); + } + assignLabels(elements, context, this); + + wire(elements, context); + + getAttributes().put(getElementsAttributeName(), elements); + + if (elements.hasValidationError()) { + getAttributes().put(getInvalidAttributeName(), true); + } + + getAttributes().put(getRequiredAttributeName(), elements.hasRequiredInput()); + + 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()); + } + } + } + + // assign label attribute for any input component (better validation message) + private void assignLabels(InputContainerElements elements, + FacesContext context, UIInputContainer uiInputContainer) { + + // label attribute handling + ValueExpression labelValueExpression = null; + Object labelValue = null; + for (int i = 0, len = elements.getInputs().size(); i < len; i++) { + UIComponent input = (UIComponent) elements.getInputs().get(i); + if (input.getValueExpression("label") == null + && (!input.getAttributes().containsKey("label"))) { + + // retrieving container label only if necessary + if (labelValueExpression == null && labelValue == null) { + labelValueExpression = uiInputContainer.getValueExpression(getLabelAttributeName()); + if (labelValueExpression == null) { + labelValue = uiInputContainer.getAttributes().get(getLabelAttributeName()); + } + } + + // copy container label to input + if (labelValueExpression != null) { + input.setValueExpression("label", labelValueExpression); + } + if (labelValue != null) { + input.getAttributes().put("label", labelValue); + } + } + } + } + + /** + * 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; + } + } + + private boolean labelHasEmptyValue(InputContainerElements elements) { + if (elements.getLabel() == null || elements.getLabel().getValue() == null) + return false; + return (elements.getLabel().getValue().toString().trim().equals(":") || elements.getLabel().getValue().toString() + .trim().equals("")); + } + + 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; + + 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.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() { + //We have to scan these each time as the value could change in an AJAX request + for (EditableValueHolder holder : inputs) { + if (holder.isRequired()) { + return true; + } + } + return false; + } + + 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)); + } + } + } + } + } } \ No newline at end of file