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 java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
25  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
26  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
27  import org.kuali.rice.krad.uif.UifConstants;
28  import org.kuali.rice.krad.uif.component.Component;
29  import org.kuali.rice.krad.uif.container.CollectionGroup;
30  import org.kuali.rice.krad.uif.container.Container;
31  import org.kuali.rice.krad.uif.container.LightTable;
32  import org.kuali.rice.krad.uif.container.PageGroup;
33  import org.kuali.rice.krad.uif.field.FieldGroup;
34  import org.kuali.rice.krad.uif.field.InputField;
35  import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
36  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
37  import org.kuali.rice.krad.uif.util.ScriptUtils;
38  import org.kuali.rice.krad.uif.view.View;
39  import org.kuali.rice.krad.util.GlobalVariables;
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-bean", parent = "Uif-GroupValidationMessages")
47  public class GroupValidationMessages extends ValidationMessages {
48      private static final long serialVersionUID = -5389990220206079052L;
49  
50      private boolean displayFieldLabelWithMessages;
51      private boolean collapseAdditionalFieldLinkMessages;
52      private boolean displayHeaderMessageSummary;
53  
54      private static final String SECTION_TOKEN = "s$";
55      private static final String FIELDGROUP_TOKEN = "f$";
56      private static final String TABLE_COLLECTION_TOKEN = "c$";
57  
58      /**
59       * {@inheritDoc}
60       */
61      @Override
62      public void generateMessages(View view, Object model, Component parent) {
63          super.generateMessages(view, model, parent);
64  
65          addValidationMessageDataAttributes(parent);
66      }
67  
68      /**
69       * Adds dataAttributes that are appropriate for group level validationMessages data.
70       *
71       * <p>This data is used by the validation framework clientside. Some special handling at this level includes
72       * retrieving the groups and fields generated by different collection layouts and handling page and fieldGroup
73       * scenarios slightly differently due to the nature of how they are built out in the js.</p>
74       *
75       * @param parent component that is the parent of the validation messages
76       */
77      protected void addValidationMessageDataAttributes(Component parent) {
78          HashMap<String, Object> validationMessagesDataAttributes = new HashMap<String, Object>();
79  
80          Map<String, Object> parentContext = parent.getContext();
81          Object parentContainer = parentContext == null ? null : parentContext.get(
82                  UifConstants.ContextVariableNames.PARENT);
83  
84          List<? extends Component> items = ((Container) parent).getItems();
85          boolean skipSections = false;
86          boolean isTableCollection = false;
87  
88          // Handle the special CollectionGroup case by getting the StackedGroups and DataFields generated by them
89          if (parent instanceof CollectionGroup &&
90                  ((CollectionGroup) parent).getLayoutManager() instanceof StackedLayoutManager) {
91              items = ((StackedLayoutManager) ((CollectionGroup) parent).getLayoutManager()).getStackedGroups();
92          } else if ((parent instanceof CollectionGroup &&
93                  ((CollectionGroup) parent).getLayoutManager() instanceof TableLayoutManager) ||
94                  parent instanceof LightTable) {
95              // order is not needed  so null items
96              items = null;
97              skipSections = true;
98              isTableCollection = true;
99          }
100 
101         List<String> sectionIds = new ArrayList<String>();
102         List<String> fieldOrder = new ArrayList<String>();
103         collectIdsFromItems(items, sectionIds, fieldOrder, skipSections);
104 
105         boolean pageLevel = false;
106         boolean forceShow = false;
107         boolean showPageSummaryHeader = true;
108         if (parent instanceof PageGroup) {
109             pageLevel = true;
110             forceShow = true;
111             parent.addDataAttribute(UifConstants.DataAttributes.SERVER_MESSAGES, Boolean.toString(
112                     GlobalVariables.getMessageMap().hasMessages()));
113             if (this instanceof PageValidationMessages) {
114                 showPageSummaryHeader = ((PageValidationMessages) this).isShowPageSummaryHeader();
115             }
116         } else if (parentContainer instanceof FieldGroup) {
117             Map<String, String> parentFieldGroupDataAttributes = ((FieldGroup) parentContainer).getDataAttributes();
118             String role = parentFieldGroupDataAttributes == null ? null : parentFieldGroupDataAttributes.get(
119                     UifConstants.DataAttributes.ROLE);
120             if (StringUtils.isNotBlank(role) && role.equals("detailsFieldGroup")) {
121                 forceShow = false;
122             } else {
123                 //note this means container of the parent is a FieldGroup
124                 forceShow = true;
125             }
126         }
127 
128         boolean hasMessages = false;
129         if (!this.getErrors().isEmpty() || !this.getWarnings().isEmpty() || !this.getInfos().isEmpty()) {
130             hasMessages = true;
131         }
132 
133         Map<String, String> dataDefaults =
134                 (Map<String, String>) (KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
135                         "Uif-GroupValidationMessages-DataDefaults"));
136 
137         //add necessary data attributes to map
138         //display related
139         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
140                 UifConstants.DataAttributes.SUMMARIZE, true);
141         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
142                 UifConstants.DataAttributes.DISPLAY_MESSAGES, this.isDisplayMessages());
143         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
144                 UifConstants.DataAttributes.COLLAPSE_FIELD_MESSAGES, collapseAdditionalFieldLinkMessages);
145         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
146                 UifConstants.DataAttributes.SHOW_PAGE_SUMMARY_HEADER, showPageSummaryHeader);
147         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
148                 UifConstants.DataAttributes.DISPLAY_LABEL, displayFieldLabelWithMessages);
149         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
150                 UifConstants.DataAttributes.DISPLAY_HEADER_SUMMARY, displayHeaderMessageSummary);
151         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
152                 UifConstants.DataAttributes.IS_TABLE_COLLECTION, isTableCollection);
153 
154         //options
155         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
156                 UifConstants.DataAttributes.HAS_OWN_MESSAGES, hasMessages);
157         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
158                 UifConstants.DataAttributes.PAGE_LEVEL, pageLevel);
159         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
160                 UifConstants.DataAttributes.FORCE_SHOW, forceShow);
161 
162         //order related
163         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
164                 UifConstants.DataAttributes.SECTIONS, sectionIds);
165         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
166                 UifConstants.DataAttributes.ORDER, fieldOrder);
167 
168         //server messages
169         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
170                 UifConstants.DataAttributes.SERVER_ERRORS, ScriptUtils.escapeHtml(this.getErrors()));
171         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
172                 UifConstants.DataAttributes.SERVER_WARNINGS, ScriptUtils.escapeHtml(this.getWarnings()));
173         this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
174                 UifConstants.DataAttributes.SERVER_INFO, ScriptUtils.escapeHtml(this.getInfos()));
175 
176         if (!validationMessagesDataAttributes.isEmpty()) {
177             parent.addScriptDataAttribute(UifConstants.DataAttributes.VALIDATION_MESSAGES, ScriptUtils.translateValue(
178                     validationMessagesDataAttributes));
179         }
180     }
181 
182     /**
183      * Collects all the ids from the items passed into this method.
184      *
185      * <p>Puts the ids of items determined to be sections
186      * into the sectionIds list, and orders all items by the order they appear on the page in the order list with
187      * special identifiers
188      * to determine the type of item they are (used by the client js).  When skipSections is true do not
189      * include sectionIds found in the lists.</p>
190      *
191      * @param items items of the group
192      * @param sectionIds list to put section ids into
193      * @param order list to put order of ids into (both fields and sections)
194      * @param skipSections skip adding sections
195      */
196     protected void collectIdsFromItems(List<? extends Component> items, List<String> sectionIds, List<String> order,
197             boolean skipSections) {
198 
199         if (items != null) {
200             for (Component component : items) {
201                 String id = StringUtils.replace( component.getId(), "@id@", "");
202                 if (component instanceof Container || component instanceof FieldGroup) {
203                     if (component instanceof FieldGroup) {
204                         if (!skipSections &&
205                                 ((FieldGroup) component).getFieldLabel().isRender() &&
206                                 !((FieldGroup) component).getFieldLabel().isHidden() &&
207                                 (StringUtils.isNotEmpty(((FieldGroup) component).getLabel()) || StringUtils.isNotEmpty(
208                                         ((FieldGroup) component).getFieldLabel().getLabelText()))) {
209                             sectionIds.add(id);
210                             order.add(FIELDGROUP_TOKEN + id);
211                             continue;
212                         } else {
213                             component = ((FieldGroup) component).getGroup();
214                             if (component == null) {
215                                 continue;
216                             }
217                         }
218                     }
219 
220                     id = StringUtils.replace( component.getId(), "@id@", "");
221                     //If any kind of header text is showing consider this group a section
222                     if (!skipSections
223                             && ((Container) component).getHeader() != null
224                             && ((Container) component).getHeader().isRender()
225                             && (StringUtils.isNotBlank(((Container) component).getHeader().getHeaderText()) || StringUtils
226                             .isNotBlank(component.getTitle()))) {
227                         sectionIds.add(id);
228                         order.add(SECTION_TOKEN + id);
229                     } else if ((component instanceof CollectionGroup
230                                     && ((CollectionGroup) component).getLayoutManager() instanceof TableLayoutManager)
231                                     || component instanceof LightTable){
232                         order.add(TABLE_COLLECTION_TOKEN + id);
233                     } else {
234                         collectIdsFromItems(((Container) component).getItems(), sectionIds, order, skipSections);
235                     }
236                 } else if (component instanceof InputField) {
237                     order.add(id);
238                 }
239             }
240         }
241     }
242 
243     /**
244      * If true, the error messages will display the an InputField's title
245      * alongside the error, warning, and info messages related to it. This
246      * setting has no effect on messages which do not relate directly to a
247      * single InputField.
248      *
249      * @return the displayFieldLabelWithMessages
250      */
251     @BeanTagAttribute(name = "displayFieldLabelWithMessages")
252     public boolean isDisplayFieldLabelWithMessages() {
253         return this.displayFieldLabelWithMessages;
254     }
255 
256     /**
257      * If true, the error messages will display the an InputField's title
258      * alongside the error, warning, and info messages related to it. This
259      * setting has no effect on messages which do not relate directly to a
260      * single InputField.
261      *
262      * @param displayFieldLabelWithMessages the displayFieldLabelWithMessages to set
263      */
264     public void setDisplayFieldLabelWithMessages(boolean displayFieldLabelWithMessages) {
265         this.displayFieldLabelWithMessages = displayFieldLabelWithMessages;
266     }
267 
268     /**
269      * When collapseAdditionalFieldLinkMessages is set to true, the messages generated on field links will be
270      * summarized to limit the space they take up with an appendage similar to [+n message type] appended for
271      * additional
272      * messages that are omitted.  When this flag is false, all messages will be part of the link separated by
273      * a comma.
274      *
275      * @return if field link messages are being collapsed
276      */
277     @BeanTagAttribute(name = "collapseAdditionalFieldLinkMessages")
278     public boolean isCollapseAdditionalFieldLinkMessages() {
279         return collapseAdditionalFieldLinkMessages;
280     }
281 
282     /**
283      * Set collapseAdditionalFieldLinkMessages
284      *
285      * @param collapseAdditionalFieldLinkMessages - true if field link messages are being collapsed
286      */
287     public void setCollapseAdditionalFieldLinkMessages(boolean collapseAdditionalFieldLinkMessages) {
288         this.collapseAdditionalFieldLinkMessages = collapseAdditionalFieldLinkMessages;
289     }
290 
291     /**
292      * If true, the header message summary will display (this is the message count message appended to section
293      * headers).
294      *
295      * @return true if the summary will display, false otherwise
296      */
297     @BeanTagAttribute(name = "displayHeaderMessageSummary")
298     public boolean isDisplayHeaderMessageSummary() {
299         return displayHeaderMessageSummary;
300     }
301 
302     /**
303      * Sets whether the header message summary will display or not for this section/page.
304      *
305      * @param displayHeaderMessageSummary
306      */
307     public void setDisplayHeaderMessageSummary(boolean displayHeaderMessageSummary) {
308         this.displayHeaderMessageSummary = displayHeaderMessageSummary;
309     }
310 }