/
OmniMessages.java
217 lines (194 loc) · 8.86 KB
/
OmniMessages.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
* Copyright 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
*
* https://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 jakarta.el.ValueExpression;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.component.FacesComponent;
import jakarta.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:
*
* <h2>Multiple <code>for</code> components</h2>
* <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>
*
* <h2>Displaying single message</h2>
* <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>
*
* <h2>HTML escaping</h2>
* <p>Control HTML escaping by the <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!
*
* <h2>Iteration markup control</h2>
* <p>Control iteration markup fully by the <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.
*
* <h2>Design notice</h2>
* <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 component type, which is {@value org.omnifaces.component.messages.OmniMessages#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);
}
}