1 /** 2 * Copyright 2005-2015 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, " ")); 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 <> are not part of the content, they specify placeholders): 155 * <ul> 156 * <li>[id=<component id>] - 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>[<html tag>][/<html tag>] - insert html content directly into the message content at that 159 * location, 160 * without the need to escape the <> characters in xml</li> 161 * <li>[color=<html color code/name>][/color] - wrap content in color tags to make text that color 162 * in the message</li> 163 * <li>[css=<css classes>][/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=<href src>][/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=<href src>][/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=<action settings> 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 }