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