View Javadoc
1   /**
2    * Copyright 2005-2016 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.service.KRADServiceLocatorWeb;
22  import org.kuali.rice.krad.uif.UifConstants;
23  import org.kuali.rice.krad.uif.component.Component;
24  import org.kuali.rice.krad.uif.container.CollectionGroup;
25  import org.kuali.rice.krad.uif.container.Container;
26  import org.kuali.rice.krad.uif.container.LightTable;
27  import org.kuali.rice.krad.uif.container.PageGroup;
28  import org.kuali.rice.krad.uif.field.FieldGroup;
29  import org.kuali.rice.krad.uif.field.InputField;
30  import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
31  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
32  import org.kuali.rice.krad.uif.util.ScriptUtils;
33  import org.kuali.rice.krad.uif.view.View;
34  import org.kuali.rice.krad.util.GlobalVariables;
35  
36  import java.util.ArrayList;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Map;
40  
41  /**
42   * ValidationMessages for logic and options specific to groups.
43   *
44   * @author Kuali Rice Team (rice.collab@kuali.org)
45   */
46  @BeanTag(name = "groupValidationMessages", parent = "Uif-GroupValidationMessages")
47  public class GroupValidationMessages extends ValidationMessages {
48  
49      private static final long serialVersionUID = -5389990220206079052L;
50  
51      private boolean closeable;
52  
53      private boolean displayFieldLabelWithMessages;
54      private boolean collapseAdditionalFieldLinkMessages;
55      private boolean displayHeaderMessageSummary;
56  
57      private static final String SECTION_TOKEN = "s$";
58      private static final String FIELDGROUP_TOKEN = "f$";
59      private static final String TABLE_COLLECTION_TOKEN = "c$";
60  
61      /**
62       * {@inheritDoc}
63       */
64      @Override
65      public void generateMessages(View view, Object model, Component parent) {
66          super.generateMessages(view, model, parent);
67  
68          addValidationMessageDataAttributes(parent);
69      }
70  
71      /**
72       * Adds dataAttributes that are appropriate for group level validationMessages data.
73       *
74       * <p>This data is used by the validation framework clientside. Some special handling at this level includes
75       * retrieving the groups and fields generated by different collection layouts and handling page and fieldGroup
76       * scenarios slightly differently due to the nature of how they are built out in the js.</p>
77       *
78       * @param parent component that is the parent of the validation messages
79       */
80      protected void addValidationMessageDataAttributes(Component parent) {
81          HashMap<String, Object> validationMessagesDataAttributes = new HashMap<String, Object>();
82  
83          Map<String, Object> parentContext = parent.getContext();
84          Object parentContainer = parentContext == null ? null : parentContext.get(
85                  UifConstants.ContextVariableNames.PARENT);
86  
87          List<? extends Component> items = ((Container) parent).getItems();
88          boolean skipSections = false;
89          boolean isTableCollection = false;
90  
91          // Handle the special CollectionGroup case by getting the StackedGroups and DataFields generated by them
92          if (parent instanceof CollectionGroup && ((CollectionGroup) parent)
93                  .getLayoutManager() instanceof StackedLayoutManager) {
94              items = ((StackedLayoutManager) ((CollectionGroup) parent).getLayoutManager()).getStackedGroups();
95          } else if ((parent instanceof CollectionGroup && ((CollectionGroup) parent)
96                  .getLayoutManager() instanceof TableLayoutManager) || parent instanceof LightTable) {
97              // order is not needed  so null items
98              items = null;
99              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 }