001/** 002 * Copyright 2005-2018 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.element; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.datadictionary.parse.BeanTag; 020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 021import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 022import org.kuali.rice.krad.uif.UifConstants; 023import org.kuali.rice.krad.uif.component.Component; 024import org.kuali.rice.krad.uif.container.CollectionGroup; 025import org.kuali.rice.krad.uif.container.Container; 026import org.kuali.rice.krad.uif.container.LightTable; 027import org.kuali.rice.krad.uif.container.PageGroup; 028import org.kuali.rice.krad.uif.field.FieldGroup; 029import org.kuali.rice.krad.uif.field.InputField; 030import org.kuali.rice.krad.uif.layout.StackedLayoutManager; 031import org.kuali.rice.krad.uif.layout.TableLayoutManager; 032import org.kuali.rice.krad.uif.util.ScriptUtils; 033import org.kuali.rice.krad.uif.view.View; 034import org.kuali.rice.krad.util.GlobalVariables; 035 036import java.util.ArrayList; 037import java.util.HashMap; 038import java.util.List; 039import java.util.Map; 040 041/** 042 * ValidationMessages for logic and options specific to groups. 043 * 044 * @author Kuali Rice Team (rice.collab@kuali.org) 045 */ 046@BeanTag(name = "groupValidationMessages", parent = "Uif-GroupValidationMessages") 047public class GroupValidationMessages extends ValidationMessages { 048 049 private static final long serialVersionUID = -5389990220206079052L; 050 051 private boolean closeable; 052 053 private boolean displayFieldLabelWithMessages; 054 private boolean collapseAdditionalFieldLinkMessages; 055 private boolean displayHeaderMessageSummary; 056 057 private static final String SECTION_TOKEN = "s$"; 058 private static final String FIELDGROUP_TOKEN = "f$"; 059 private static final String TABLE_COLLECTION_TOKEN = "c$"; 060 061 /** 062 * {@inheritDoc} 063 */ 064 @Override 065 public void generateMessages(View view, Object model, Component parent) { 066 super.generateMessages(view, model, parent); 067 068 addValidationMessageDataAttributes(parent); 069 } 070 071 /** 072 * Adds dataAttributes that are appropriate for group level validationMessages data. 073 * 074 * <p>This data is used by the validation framework clientside. Some special handling at this level includes 075 * retrieving the groups and fields generated by different collection layouts and handling page and fieldGroup 076 * scenarios slightly differently due to the nature of how they are built out in the js.</p> 077 * 078 * @param parent component that is the parent of the validation messages 079 */ 080 protected void addValidationMessageDataAttributes(Component parent) { 081 HashMap<String, Object> validationMessagesDataAttributes = new HashMap<String, Object>(); 082 083 Map<String, Object> parentContext = parent.getContext(); 084 Object parentContainer = parentContext == null ? null : parentContext.get( 085 UifConstants.ContextVariableNames.PARENT); 086 087 List<? extends Component> items = ((Container) parent).getItems(); 088 boolean skipSections = false; 089 boolean isTableCollection = false; 090 091 // Handle the special CollectionGroup case by getting the StackedGroups and DataFields generated by them 092 if (parent instanceof CollectionGroup && ((CollectionGroup) parent) 093 .getLayoutManager() instanceof StackedLayoutManager) { 094 items = ((StackedLayoutManager) ((CollectionGroup) parent).getLayoutManager()).getStackedGroups(); 095 } else if ((parent instanceof CollectionGroup && ((CollectionGroup) parent) 096 .getLayoutManager() instanceof TableLayoutManager) || parent instanceof LightTable) { 097 // order is not needed so null items 098 items = null; 099 skipSections = true; 100 isTableCollection = true; 101 } 102 103 List<String> sectionIds = new ArrayList<String>(); 104 List<String> fieldOrder = new ArrayList<String>(); 105 collectIdsFromItems(items, sectionIds, fieldOrder, skipSections); 106 107 boolean pageLevel = false; 108 boolean forceShow = false; 109 boolean showPageSummaryHeader = true; 110 if (parent instanceof PageGroup) { 111 pageLevel = true; 112 forceShow = true; 113 parent.addDataAttribute(UifConstants.DataAttributes.SERVER_MESSAGES, Boolean.toString( 114 GlobalVariables.getMessageMap().hasMessages() || !GlobalVariables.getAuditErrorMap().isEmpty())); 115 if (this instanceof PageValidationMessages) { 116 showPageSummaryHeader = ((PageValidationMessages) this).isShowPageSummaryHeader(); 117 } 118 } else if (parentContainer instanceof FieldGroup) { 119 Map<String, String> parentFieldGroupDataAttributes = ((FieldGroup) parentContainer).getDataAttributes(); 120 String role = parentFieldGroupDataAttributes == null ? null : parentFieldGroupDataAttributes.get( 121 UifConstants.DataAttributes.ROLE); 122 if (StringUtils.isNotBlank(role) && role.equals("detailsFieldGroup")) { 123 forceShow = false; 124 } else { 125 //note this means container of the parent is a FieldGroup 126 forceShow = true; 127 } 128 } 129 130 boolean hasMessages = false; 131 if (!this.getErrors().isEmpty() || !this.getWarnings().isEmpty() || !this.getInfos().isEmpty()) { 132 hasMessages = true; 133 } 134 135 Map<String, String> dataDefaults = 136 (Map<String, String>) (KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean( 137 "Uif-GroupValidationMessages-DataDefaults")); 138 139 //add necessary data attributes to map 140 //display related 141 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 142 UifConstants.DataAttributes.SUMMARIZE, true); 143 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 144 UifConstants.DataAttributes.DISPLAY_MESSAGES, this.isDisplayMessages()); 145 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 146 UifConstants.DataAttributes.CLOSEABLE, this.isCloseable()); 147 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 148 UifConstants.DataAttributes.COLLAPSE_FIELD_MESSAGES, collapseAdditionalFieldLinkMessages); 149 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 150 UifConstants.DataAttributes.SHOW_PAGE_SUMMARY_HEADER, showPageSummaryHeader); 151 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 152 UifConstants.DataAttributes.DISPLAY_LABEL, displayFieldLabelWithMessages); 153 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 154 UifConstants.DataAttributes.DISPLAY_HEADER_SUMMARY, displayHeaderMessageSummary); 155 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 156 UifConstants.DataAttributes.IS_TABLE_COLLECTION, isTableCollection); 157 158 //options 159 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 160 UifConstants.DataAttributes.HAS_OWN_MESSAGES, hasMessages); 161 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 162 UifConstants.DataAttributes.PAGE_LEVEL, pageLevel); 163 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 164 UifConstants.DataAttributes.FORCE_SHOW, forceShow); 165 166 //order related 167 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 168 UifConstants.DataAttributes.SECTIONS, sectionIds); 169 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 170 UifConstants.DataAttributes.ORDER, fieldOrder); 171 172 //server messages 173 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 174 UifConstants.DataAttributes.SERVER_ERRORS, ScriptUtils.escapeHtml(this.getErrors())); 175 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 176 UifConstants.DataAttributes.SERVER_WARNINGS, ScriptUtils.escapeHtml(this.getWarnings())); 177 this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults, 178 UifConstants.DataAttributes.SERVER_INFO, ScriptUtils.escapeHtml(this.getInfos())); 179 180 if (!validationMessagesDataAttributes.isEmpty()) { 181 parent.addScriptDataAttribute(UifConstants.DataAttributes.VALIDATION_MESSAGES, ScriptUtils.translateValue( 182 validationMessagesDataAttributes)); 183 } 184 } 185 186 /** 187 * Collects all the ids from the items passed into this method. 188 * 189 * <p>Puts the ids of items determined to be sections 190 * into the sectionIds list, and orders all items by the order they appear on the page in the order list with 191 * special identifiers 192 * to determine the type of item they are (used by the client js). When skipSections is true do not 193 * include sectionIds found in the lists.</p> 194 * 195 * @param items items of the group 196 * @param sectionIds list to put section ids into 197 * @param order list to put order of ids into (both fields and sections) 198 * @param skipSections skip adding sections 199 */ 200 protected void collectIdsFromItems(List<? extends Component> items, List<String> sectionIds, List<String> order, 201 boolean skipSections) { 202 203 if (items == null) { 204 return; 205 } 206 207 for (Component component : items) { 208 String id = StringUtils.replace(component.getId(), "@id@", ""); 209 210 if (component instanceof FieldGroup) { 211 boolean treatFieldGroupAsSection = ((FieldGroup) component).getFieldLabel().isRender() && 212 !((FieldGroup) component).getFieldLabel().isHidden() && 213 (StringUtils.isNotEmpty(((FieldGroup) component).getLabel()) || StringUtils.isNotEmpty( 214 ((FieldGroup) component).getFieldLabel().getLabelText())); 215 216 if (!skipSections && treatFieldGroupAsSection) { 217 sectionIds.add(id); 218 219 // Add with field group identifier 220 order.add(FIELDGROUP_TOKEN + id); 221 continue; 222 } else { 223 component = ((FieldGroup) component).getGroup(); 224 if (component == null) { 225 continue; 226 } 227 } 228 } 229 230 // Not an 'else if' because component being evaluated can change to container above^ 231 if (component instanceof Container) { 232 id = StringUtils.replace(component.getId(), "@id@", ""); 233 234 //If any kind of header text is showing consider this group a section 235 boolean isSection = 236 ((Container) component).getHeader() != null && ((Container) component).getHeader().isRender() 237 && (StringUtils.isNotBlank(((Container) component).getHeader().getHeaderText()) 238 || StringUtils.isNotBlank(component.getTitle())); 239 240 if (!skipSections && isSection) { 241 sectionIds.add(id); 242 243 // Add with section identifier 244 order.add(SECTION_TOKEN + id); 245 } else if ((component instanceof CollectionGroup && ((CollectionGroup) component) 246 .getLayoutManager() instanceof TableLayoutManager) || component instanceof LightTable) { 247 // Add with collection identifier 248 order.add(TABLE_COLLECTION_TOKEN + id); 249 } else { 250 // If a non-section container, collect ids from sub items 251 collectIdsFromItems(((Container) component).getItems(), sectionIds, order, skipSections); 252 } 253 } else if (component instanceof InputField) { 254 order.add(id); 255 } 256 } 257 } 258 259 /** 260 * If true, validation message divs are closeable(dismissable) by the user and 261 * they do not return until another page level validation/component refresh/page refresh is invoked 262 * 263 * @return true 264 */ 265 public boolean isCloseable() { 266 return closeable; 267 } 268 269 /** 270 * @see GroupValidationMessages#isCloseable() 271 */ 272 public void setCloseable(boolean closeable) { 273 this.closeable = closeable; 274 } 275 276 /** 277 * If true, the error messages will display the an InputField's title 278 * alongside the error, warning, and info messages related to it. This 279 * setting has no effect on messages which do not relate directly to a 280 * single InputField. 281 * 282 * @return the displayFieldLabelWithMessages 283 */ 284 @BeanTagAttribute 285 public boolean isDisplayFieldLabelWithMessages() { 286 return this.displayFieldLabelWithMessages; 287 } 288 289 /** 290 * If true, the error messages will display the an InputField's title 291 * alongside the error, warning, and info messages related to it. This 292 * setting has no effect on messages which do not relate directly to a 293 * single InputField. 294 * 295 * @param displayFieldLabelWithMessages the displayFieldLabelWithMessages to set 296 */ 297 public void setDisplayFieldLabelWithMessages(boolean displayFieldLabelWithMessages) { 298 this.displayFieldLabelWithMessages = displayFieldLabelWithMessages; 299 } 300 301 /** 302 * When collapseAdditionalFieldLinkMessages is set to true, the messages generated on field links will be 303 * summarized to limit the space they take up with an appendage similar to [+n message type] appended for 304 * additional 305 * messages that are omitted. When this flag is false, all messages will be part of the link separated by 306 * a comma. 307 * 308 * @return if field link messages are being collapsed 309 */ 310 @BeanTagAttribute 311 public boolean isCollapseAdditionalFieldLinkMessages() { 312 return collapseAdditionalFieldLinkMessages; 313 } 314 315 /** 316 * Set collapseAdditionalFieldLinkMessages 317 * 318 * @param collapseAdditionalFieldLinkMessages - true if field link messages are being collapsed 319 */ 320 public void setCollapseAdditionalFieldLinkMessages(boolean collapseAdditionalFieldLinkMessages) { 321 this.collapseAdditionalFieldLinkMessages = collapseAdditionalFieldLinkMessages; 322 } 323 324 /** 325 * If true, the header message summary will display (this is the message count message appended to section 326 * headers). 327 * 328 * @return true if the summary will display, false otherwise 329 */ 330 @BeanTagAttribute 331 public boolean isDisplayHeaderMessageSummary() { 332 return displayHeaderMessageSummary; 333 } 334 335 /** 336 * Sets whether the header message summary will display or not for this section/page. 337 */ 338 public void setDisplayHeaderMessageSummary(boolean displayHeaderMessageSummary) { 339 this.displayHeaderMessageSummary = displayHeaderMessageSummary; 340 } 341}