/* * Copyright 2017 OmniFaces * * 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.omnifaces.component.messages; import javax.el.ValueExpression; import javax.faces.application.FacesMessage; import javax.faces.component.FacesComponent; import javax.faces.component.html.HtmlMessages; import org.omnifaces.renderer.MessagesRenderer; import org.omnifaces.util.Messages; import org.omnifaces.util.State; /** * <p> * The <code><o:messages></code> is a component that extends the standard <code><h:messages></code> with * the following new features: * * <h3>Multiple <code>for</code> components</h3> * <p> * Possibility to specify multiple client IDs space separated in the <code>for</code> attribute. The example below * would only display messages for <code>input1</code> and <code>input3</code>: * <pre> * <h:form> * <o:messages for="input1 input3" /> * <h:inputText id="input1" /> * <h:inputText id="input2" /> * <h:inputText id="input3" /> * <h:inputText id="input4" /> * </h:form> * </pre> * <p> * It can even refer non-input components which in turn contains input components. The example below would only display * messages for <code>input1</code> and <code>input2</code>: * <pre> * <h:form> * <o:messages for="inputs" /> * <h:panelGroup id="inputs"> * <h:inputText id="input1" /> * <h:inputText id="input2" /> * </h:panelGroup> * <h:inputText id="input3" /> * <h:inputText id="input4" /> * </h:form> * </pre> * <p> * You can even combine them. The example below would only display messages for <code>input1</code>, * <code>input2</code> and <code>input4</code>. * <pre> * <h:form> * <o:messages for="inputs input4" /> * <h:panelGroup id="inputs"> * <h:inputText id="input1" /> * <h:inputText id="input2" /> * </h:panelGroup> * <h:inputText id="input3" /> * <h:inputText id="input4" /> * </h:form> * </pre> * * <h3>Displaying single message</h3> * <p> * Show a single custom message whenever the component has received any faces message. This is particularly useful * when you want to display a global message in case any of the in <code>for</code> specified components has a faces * message. For example: * <pre> * <o:messages for="form" message="There are validation errors. Please fix them." /> * <h:form id="form"> * <h:inputText id="input1" /><h:message for="input1" /> * <h:inputText id="input2" /><h:message for="input2" /> * <h:inputText id="input3" /><h:message for="input3" /> * </h:form> * </pre> * * <h3>HTML escaping</h3> * <p>Control HTML escaping by the new <code>escape</code> attribute. * <pre> * <o:messages escape="false" /> * </pre> * <p>Beware of potential XSS attack holes when user-controlled input is redisplayed through messages! * * <h3>Iteration markup control</h3> * <p>Control iteration markup fully by the new <code>var</code> attribute which sets the current {@link FacesMessage} * in the request scope and disables the default table/list rendering. For example, * <pre> * <dl> * <o:messages var="message"> * <dt>#{message.severity}</dt> * <dd title="#{message.detail}">#{message.summary}</dd> * </o:messages> * </dl> * </pre> * <p>Note: the iteration is by design completely stateless. It's therefore not recommended to nest form components inside * the <code><o:messages></code> component. It should be used for pure output only, like as the standard * <code><h:messages></code>. Plain output links are however no problem. Also note that the <code>message</code> * and <code>escape</code> attributes have in this case no effect. With a single message, there's no point of * iteration. As to escaping, just use <code><h:outputText escape="false"></code> the usual way. * * <h3>Design notice</h3> * <p>The component class is named <code>OmniMessages</code> instead of <code>Messages</code> to avoid * confusion with the {@link Messages} utility class. * * @author Bauke Scholtz * @since 1.5 * @see MessagesRenderer */ @FacesComponent(OmniMessages.COMPONENT_TYPE) public class OmniMessages extends HtmlMessages { // Public constants ----------------------------------------------------------------------------------------------- /** The standard component type. */ public static final String COMPONENT_TYPE = "org.omnifaces.component.messages.OmniMessages"; // Private constants ---------------------------------------------------------------------------------------------- private static final String ERROR_EXPRESSION_DISALLOWED = "A value expression is disallowed on 'var' attribute of OmniMessages."; private enum PropertyKeys { // Cannot be uppercased. They have to exactly match the attribute names. var, message, escape; } // Variables ------------------------------------------------------------------------------------------------------ private final State state = new State(getStateHelper()); // Constructors --------------------------------------------------------------------------------------------------- /** * Construct a new {@link OmniMessages} component whereby the renderer type is set to * {@link MessagesRenderer#RENDERER_TYPE}. */ public OmniMessages() { setRendererType(MessagesRenderer.RENDERER_TYPE); } // Actions -------------------------------------------------------------------------------------------------------- /** * An override which checks if this isn't been invoked on <code>var</code> attribute. * Finally it delegates to the super method. * @throws IllegalArgumentException When this value expression is been set on <code>var</code> attribute. */ @Override public void setValueExpression(String name, ValueExpression binding) { if (PropertyKeys.var.toString().equals(name)) { throw new IllegalArgumentException(ERROR_EXPRESSION_DISALLOWED); } super.setValueExpression(name, binding); } // Attribute getters/setters -------------------------------------------------------------------------------------- /** * Returns the name of the request attribute which exposes the current faces message. * @return The name of the request attribute which exposes the current faces message. */ public String getVar() { return state.get(PropertyKeys.var); } /** * Sets the name of the request attribute which exposes the current faces message. * @param var The name of the request attribute which exposes the current faces message. */ public void setVar(String var) { state.put(PropertyKeys.var, var); } /** * Returns the single INFO message to be shown instead when this component has any faces message. * @return The single INFO message to be shown instead when this component has any faces message. * @since 1.6 */ public String getMessage() { return state.get(PropertyKeys.message); } /** * Sets the single INFO message to be shown instead when this component has any faces message. * @param message The single INFO message to be shown instead when this component has any faces message. * @since 1.6 */ public void setMessage(String message) { state.put(PropertyKeys.message, message); } /** * Returns whether the message detail and summary should be HTML-escaped. Defaults to <code>true</code>. * @return Whether the message detail and summary should be HTML-escaped. */ public boolean isEscape() { return state.get(PropertyKeys.escape, true); } /** * Sets whether the message detail and summary should be HTML-escaped. * @param escape Whether the message detail and summary should be HTML-escaped. */ public void setEscape(boolean escape) { state.put(PropertyKeys.escape, escape); } }