View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.element;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
22  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
23  import org.kuali.rice.krad.datadictionary.validator.Validator;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.component.Component;
26  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
27  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
28  import org.kuali.rice.krad.uif.util.ComponentUtils;
29  import org.kuali.rice.krad.uif.util.LifecycleElement;
30  import org.kuali.rice.krad.uif.util.MessageStructureUtils;
31  import org.kuali.rice.krad.util.KRADConstants;
32  
33  import java.util.List;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  
37  /**
38   * Encapsulates a text message to be displayed
39   *
40   * <p>
41   * The <code>Message</code> is used to display static text in the user interface
42   * </p>
43   *
44   * @author Kuali Rice Team (rice.collab@kuali.org)
45   */
46  @BeanTag(name = "message", parent = "Uif-Message")
47  public class Message extends ContentElementBase {
48      private static final long serialVersionUID = 4090058533452450395L;
49  
50      // This regex is a check to see if the message is a rich message and it contains potential non-inline elements
51      private static Pattern blockElementCheck = Pattern.compile(
52              "[\\[|\\<](?!color|action|link|css|button|input|label|select|textarea|abbr"
53                      + "|strong|img|a[\\s\\]]|span[\\s\\]]|b[\\s\\]]|i[\\s\\]]|br[\\s\\]/])[^/]*?/?[\\]|\\>]");
54  
55      private String messageText;
56      private String wrapperTag;
57      private boolean renderWrapperTag;
58  
59      private List<Component> inlineComponents;
60      private List<Component> messageComponentStructure;
61  
62      private boolean parseComponents;
63      private boolean richMessage;
64      private boolean containsBlockElements;
65  
66      public Message() {
67          super();
68  
69          renderWrapperTag = true;
70          parseComponents = true;
71      }
72  
73      /**
74       * Message perform apply model parses message text for rich text functionality if the messageText contains
75       * [ or ] special characters
76       *
77       * {@inheritDoc}
78       */
79      @Override
80      public void performApplyModel(Object model, LifecycleElement parent) {
81          super.performApplyModel(model, parent);
82  
83          //if messageText contains the special characters [] then parse and fill in the messageComponentStructure
84          //but if messageComponentStructure has already been set it overrides messageText by default
85          if (messageText != null && messageText.contains(KRADConstants.MessageParsing.LEFT_TOKEN) &&
86                  messageText.contains(KRADConstants.MessageParsing.RIGHT_TOKEN) &&
87                  (messageComponentStructure == null || messageComponentStructure.isEmpty())) {
88              richMessage = true;
89  
90              // Check to see if message contains potential block elements (non-inline)
91              Matcher matcher = blockElementCheck.matcher(messageText);
92              containsBlockElements = matcher.find();
93  
94              if (StringUtils.isBlank(wrapperTag) && containsBlockElements) {
95                  wrapperTag = UifConstants.WrapperTags.DIV;
96              }
97  
98              messageComponentStructure = MessageStructureUtils.parseMessage(this.getId(), this.getMessageText(),
99                      this.getInlineComponents(), ViewLifecycle.getView(), parseComponents);
100         } else if (messageText != null && messageText.contains("<") && messageText.contains(">")) {
101             // Straight inline html case
102             // Check to see if message contains potential block elements (non-inline)
103             Matcher matcher = blockElementCheck.matcher(messageText);
104             containsBlockElements = matcher.find();
105 
106             // Must be in a div it contains potential block elements
107             if (StringUtils.isBlank(wrapperTag) && containsBlockElements) {
108                 wrapperTag = UifConstants.WrapperTags.DIV;
109             }
110         }
111 
112         // If the wrapper element is not set by the bean def or the above logic to check for block elements, default
113         // to the p tag
114         if (StringUtils.isBlank(wrapperTag)) {
115             wrapperTag = UifConstants.WrapperTags.P;
116         }
117     }
118 
119     /**
120      * {@inheritDoc}
121      */
122     @Override
123     public void performFinalize(Object model, LifecycleElement parent) {
124         super.performFinalize(model, parent);
125 
126         if (messageComponentStructure != null && !messageComponentStructure.isEmpty()) {
127             // Message needs to be aware of its own parent because it now contains content that can have validation
128             this.addDataAttribute(UifConstants.DataAttributes.PARENT, parent.getId());
129         }
130     }
131 
132     /**
133      * Override to render only if the message text has been given or there is a conditional expression on the
134      * message text
135      *
136      * {@inheritDoc}
137      */
138     @Override
139     public boolean isRender() {
140         boolean render = super.isRender();
141 
142         if (render) {
143             render = getPropertyExpressions().containsKey("messageText") || (StringUtils.isNotBlank(messageText)
144                     && !StringUtils.equals(messageText, "&nbsp;"));
145         }
146 
147         return render;
148     }
149 
150     /**
151      * Text that makes up the message that will be displayed.
152      *
153      * <p>If special characters [] are detected the message is split at that location.  The types of features supported
154      * by the parse are (note that &lt;&gt; are not part of the content, they specify placeholders):
155      * <ul>
156      * <li>[id=&lt;component id&gt;] - insert component with id specified at that location in the message</li>
157      * <li>[n] - insert component at index n from the inlineComponent list</li>
158      * <li>[&lt;html tag&gt;][/&lt;html tag&gt;] - insert html content directly into the message content at that
159      * location,
160      * without the need to escape the &lt;&gt; characters in xml</li>
161      * <li>[color=&lt;html color code/name&gt;][/color] - wrap content in color tags to make text that color
162      * in the message</li>
163      * <li>[css=&lt;css classes&gt;][/css] - apply css classes specified to the wrapped content - same as wrapping
164      * the content in span with class property set</li>
165      * <li>[link=&lt;href src&gt;][/link] - an easier way to create an anchor that will open in a new page to the
166      * href specified after =</li>
167      * <li>[action=&lt;href src&gt;][/action] - create an action link inline without having to specify a component by
168      * id or index.  The options for this are as follows and MUST be in a comma separated list in the order specified
169      * (specify 1-4 always in this order):
170      * <ul>
171      * <li>methodToCall(String)</li>
172      * <li>validateClientSide(boolean) - true if not set</li>
173      * <li>ajaxSubmit(boolean) - true if not set</li>
174      * <li>successCallback(js function or function declaration) - this only works when ajaxSubmit is true</li>
175      * </ul>
176      * The tag would look something like this [action=methodToCall]Action[/action] in most common cases.  And in more
177      * complex cases [action=methodToCall,true,true,functionName]Action[/action].  <p>In addition to these settings,
178      * you can also specify data to send to the server in this fashion (space is required between settings and data):
179      * </p>
180      * [action=&lt;action settings&gt; data={key1: 'value 1', key2: value2}]
181      * </li>
182      * </ul>
183      * If the [] characters are needed in message text, they need to be declared with an escape character: \\[ \\]
184      * </p>
185      *
186      * @return message text
187      */
188     @BeanTagAttribute
189     public String getMessageText() {
190         return this.messageText;
191     }
192 
193     /**
194      * Setter for the message text
195      *
196      * @param messageText
197      */
198     public void setMessageText(String messageText) {
199         this.messageText = messageText;
200     }
201 
202     /**
203      * Defines the html tag that will wrap this message, if left blank, this will automatically be set by the framework
204      * to the appropriate tag (in most cases p or div)
205      *
206      * @return the html tag used to wrap this message
207      */
208     @BeanTagAttribute
209     public String getWrapperTag() {
210         return wrapperTag;
211     }
212 
213     /**
214      * @see org.kuali.rice.krad.uif.element.Message#getWrapperTag()
215      */
216     public void setWrapperTag(String wrapperTag) {
217         this.wrapperTag = wrapperTag;
218     }
219 
220     /**
221      * If true, render the wrapper element (p or div) around this message (default true).
222      *
223      * <p>The wrapper will be either a p tag, for when the element only contains inline elements, or a div tag, for
224      * when the message might contain block level elements or undetermined html tags resulting from rich message
225      * functionality.  When false, skips the wrapper generation for this
226      * message - this has the additional effect the css classes/style classes will be lost for this message. </p>
227      *
228      * @return true if generating a wrapping span, false otherwise
229      */
230     @BeanTagAttribute
231     public boolean isRenderWrapperTag() {
232         return renderWrapperTag && wrapperTag != null;
233     }
234 
235     /**
236      * Sets the generate wrapper element flag
237      *
238      * @param renderWrapperTag
239      */
240     public void setRenderWrapperTag(boolean renderWrapperTag) {
241         this.renderWrapperTag = renderWrapperTag;
242     }
243 
244     /**
245      * The message component structure is a list of components which represent the components that make up a message
246      * when using rich message functionality.
247      *
248      * <p>The structure represents the parsed messageText when not set. Normally this structure is setup by the Message
249      * class and <b>SHOULD NOT BE SET</b> in xml, unless full control over the structure is needed.</p>
250      *
251      * @return list of components which represent the message structure
252      */
253     public List<Component> getMessageComponentStructure() {
254         return messageComponentStructure;
255     }
256 
257     /**
258      * Set the message component structure.  This will override/ignore messageText when set. Normally
259      * this <b>SHOULD NOT BE SET</b> by the xml configuration.
260      *
261      * @param messageComponentStructure list of components which represent the message structure
262      */
263     public void setMessageComponentStructure(List<Component> messageComponentStructure) {
264         this.messageComponentStructure = messageComponentStructure;
265     }
266 
267     /**
268      * The inlineComponents are a list of components in order by index.
269      *
270      * <p>inlineComponents is only used when the message is using rich message functionality.  A message
271      * with [0] will reference component at index 0 of this list and insert it at that place in the message,
272      * and likewise [1] will reference item 1, etc.  If the index referenced is out of bounds (or list doesn't exist),
273      * an error will be thrown during message parse.</p>
274      *
275      * @return the inlineComponents to be filled in at indexes referenced by [n] in the message
276      */
277     @BeanTagAttribute
278     @ViewLifecycleRestriction(exclude = { UifConstants.ViewPhases.APPLY_MODEL, UifConstants.ViewPhases.FINALIZE })
279     public List<Component> getInlineComponents() {
280         return inlineComponents;
281     }
282 
283     /**
284      * Set the inlineComponents to be filled in at indexes referenced by [n] in the message
285      *
286      * @param inlineComponents the inlineComponents to be filled in at indexes referenced by [n] in the message
287      */
288     public void setInlineComponents(List<Component> inlineComponents) {
289         this.inlineComponents = inlineComponents;
290     }
291 
292     /**
293      * Indicates if the inline components must be parsed for rich messages
294      *
295      * @return boolean
296      */
297     @BeanTagAttribute
298     public boolean isParseComponents() {
299         return parseComponents;
300     }
301 
302     /**
303      * Sets the parse components flag to indicate if inline components must be parsed for rich messages
304      *
305      * @param parseComponents
306      */
307     public void setParseComponents(boolean parseComponents) {
308         this.parseComponents = parseComponents;
309     }
310 
311     /**
312      * If this message is considered a rich message (is using some rich message functionality with by using
313      * the special [] tags), returns true
314      *
315      * @return return true if this message contains rich message content
316      */
317     public boolean isRichMessage() {
318         return richMessage;
319     }
320 
321     /**
322      * True if the message contains block elements, or when it contains an unknown tag that may be a block element.
323      *
324      * @return true when the message contains block elements (non-inline elements), false otherwise
325      */
326     public boolean isContainsBlockElements() {
327         return containsBlockElements;
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     @Override
334     public void completeValidation(ValidationTrace tracer) {
335         tracer.addBean(this);
336 
337         // Checks that text is set
338         if (getMessageText() == null) {
339             if (Validator.checkExpressions(this, "messageText")) {
340                 String currentValues[] = {"messageText  =" + getMessageText()};
341                 tracer.createWarning("MessageText should be set", currentValues);
342             }
343         }
344 
345         super.completeValidation(tracer.getCopy());
346     }
347 }