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.container;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Set;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
27  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
28  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
29  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
30  import org.kuali.rice.krad.datadictionary.validator.Validator;
31  import org.kuali.rice.krad.uif.UifConstants;
32  import org.kuali.rice.krad.uif.UifPropertyPaths;
33  import org.kuali.rice.krad.uif.component.Component;
34  import org.kuali.rice.krad.uif.component.DataBinding;
35  import org.kuali.rice.krad.uif.component.DelayedCopy;
36  import org.kuali.rice.krad.uif.field.Field;
37  import org.kuali.rice.krad.uif.field.FieldGroup;
38  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
39  import org.kuali.rice.krad.uif.util.ExpressionUtils;
40  import org.kuali.rice.krad.uif.util.LifecycleElement;
41  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
42  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
43  import org.kuali.rice.krad.uif.view.View;
44  import org.kuali.rice.krad.uif.view.ViewModel;
45  import org.kuali.rice.krad.uif.widget.Disclosure;
46  import org.kuali.rice.krad.uif.widget.Help;
47  import org.kuali.rice.krad.uif.widget.Scrollpane;
48  import org.kuali.rice.krad.util.KRADUtils;
49  
50  /**
51   * Container that holds a list of <code>Field</code> or other <code>Group</code>
52   * instances
53   *
54   * <p>
55   * Groups can exist at different levels of the <code>View</code>, providing
56   * conceptual groupings such as the page, section, and group. In addition, other
57   * group types can be created to add behavior like collection support
58   * </p>
59   *
60   * <p>
61   * <code>Group</code> implementation has properties for defaulting the binding
62   * information (such as the parent object path and a binding prefix) for the
63   * fields it contains. During the phase these properties (if given) are set on
64   * the fields contained in the <code>Group</code> that implement
65   * <code>DataBinding</code>, unless they have already been set on the field.
66   * </p>
67   *
68   * @author Kuali Rice Team (rice.collab@kuali.org)
69   */
70  @BeanTags({@BeanTag(name = "group", parent = "Uif-GroupBase"),
71          @BeanTag(name = "boxGroup", parent = "Uif-BoxGroupBase"),
72          @BeanTag(name = "verticalGroup", parent = "Uif-VerticalBoxGroup"),
73          @BeanTag(name = "verticalSection", parent = "Uif-VerticalBoxSection"),
74          @BeanTag(name = "verticalSubSection", parent = "Uif-VerticalBoxSubSection"),
75          @BeanTag(name = "disclosureVerticalSection", parent = "Uif-Disclosure-VerticalBoxSection"),
76          @BeanTag(name = "disclosureVerticalSubSection", parent = "Uif-Disclosure-VerticalBoxSubSection"),
77          @BeanTag(name = "horizontalGroup", parent = "Uif-HorizontalBoxGroup"),
78          @BeanTag(name = "horizontalSection", parent = "Uif-HorizontalBoxSection"),
79          @BeanTag(name = "horizontalSubSection", parent = "Uif-HorizontalBoxSubSection"),
80          @BeanTag(name = "disclosureHorizontalSection", parent = "Uif-Disclosure-HorizontalBoxSection"),
81          @BeanTag(name = "disclosureHorizontalSubSection", parent = "Uif-Disclosure-HorizontalBoxSubSection"),
82          @BeanTag(name = "grid", parent = "Uif-GridGroup"),
83          @BeanTag(name = "gridSection", parent = "Uif-GridSection"),
84          @BeanTag(name = "gridSubSection", parent = "Uif-GridSubSection"),
85          @BeanTag(name = "disclosureGridSection", parent = "Uif-Disclosure-GridSection"),
86          @BeanTag(name = "cssGrid", parent = "Uif-CssGridGroup"),
87          @BeanTag(name = "section", parent = "Uif-CssGridSection"),
88          @BeanTag(name = "subSection", parent = "Uif-CssGridSubSection"),
89          @BeanTag(name = "section1Col", parent = "Uif-CssGridSection-1FieldLabelColumn"),
90          @BeanTag(name = "section2Col", parent = "Uif-CssGridSection-2FieldLabelColumn"),
91          @BeanTag(name = "section3Col", parent = "Uif-CssGridSection-3FieldLabelColumn"),
92          @BeanTag(name = "subSection1Col", parent = "Uif-CssGridSubSection-1FieldLabelColumn"),
93          @BeanTag(name = "subSection2Col", parent = "Uif-CssGridSubSection-2FieldLabelColumn"),
94          @BeanTag(name = "subSection3Col", parent = "Uif-CssGridSubSection-3FieldLabelColumn"),
95          @BeanTag(name = "list", parent = "Uif-ListGroup"),
96          @BeanTag(name = "listSection", parent = "Uif-ListSection"),
97          @BeanTag(name = "listSubSection", parent = "Uif-ListSubSection"),
98          @BeanTag(name = "disclosureListSection", parent = "Uif-Disclosure-ListSection"),
99          @BeanTag(name = "disclosureListSubSection", parent = "Uif-Disclosure-ListSubSection"),
100         @BeanTag(name = "collectionGridItem", parent = "Uif-CollectionGridItem"),
101         @BeanTag(name = "collectionVerticalBoxItem", parent = "Uif-CollectionVerticalBoxItem"),
102         @BeanTag(name = "collectionHorizontalBoxItem", parent = "Uif-CollectionHorizontalBoxItem"),
103         @BeanTag(name = "headerUpperGroup", parent = "Uif-HeaderUpperGroup"),
104         @BeanTag(name = "headerRightGroup", parent = "Uif-HeaderRightGroup"),
105         @BeanTag(name = "headerLowerGroup", parent = "Uif-HeaderLowerGroup"),
106         @BeanTag(name = "footer", parent = "Uif-FooterBase"),
107         @BeanTag(name = "formFooter", parent = "Uif-FormFooter"),
108         @BeanTag(name = "maintenanceGrid", parent = "Uif-MaintenanceGridGroup"),
109         @BeanTag(name = "maintenanceHorizontalGroup", parent = "Uif-MaintenanceHorizontalBoxGroup"),
110         @BeanTag(name = "maintenanceVerticalGroup", parent = "Uif-MaintenanceVerticalBoxGroup"),
111         @BeanTag(name = "maintenanceGridSection", parent = "Uif-MaintenanceGridSection"),
112         @BeanTag(name = "maintenanceGridSubSection", parent = "Uif-MaintenanceGridSubSection"),
113         @BeanTag(name = "maintenanceHorizontalSection", parent = "Uif-MaintenanceHorizontalBoxSection"),
114         @BeanTag(name = "maintenanceVerticalSection", parent = "Uif-MaintenanceVerticalBoxSection"),
115         @BeanTag(name = "maintenanceHorizontalSubSection", parent = "Uif-MaintenanceHorizontalBoxSubSection"),
116         @BeanTag(name = "maintenanceVerticalSubSection", parent = "Uif-MaintenanceVerticalBoxSubSection")})
117 public class GroupBase extends ContainerBase implements Group {
118     private static final long serialVersionUID = 7953641325356535509L;
119 
120     private String fieldBindByNamePrefix;
121     private String fieldBindingObjectPath;
122 
123     @DelayedCopy
124     private Disclosure disclosure;
125     private Scrollpane scrollpane;
126 
127     private List<? extends Component> items;
128 
129     private String wrapperTag;
130 
131     /**
132      * Default Constructor
133      */
134     public GroupBase() {
135         items = Collections.emptyList();
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     @Override
142     public void performInitialization(Object model) {
143         if (isAjaxDisclosureGroup()) {
144             this.setItems(new ArrayList<Component>());
145         }
146 
147         super.performInitialization(model);
148 
149         Iterator<? extends Component> itemIterator = getItems().iterator();
150         while (itemIterator.hasNext()) {
151             Component component = itemIterator.next();
152 
153             if (component == null) {
154                 continue;
155             }
156 
157             String excludeUnless = component.getExcludeUnless();
158             if (StringUtils.isNotBlank(excludeUnless) &&
159                     !Boolean.TRUE.equals(ObjectPropertyUtils.getPropertyValue(model, excludeUnless))) {
160                 itemIterator.remove();
161                 continue;
162             }
163 
164             String excludeIf = component.getExcludeIf();
165             if (StringUtils.isNotBlank(excludeIf) &&
166                     Boolean.TRUE.equals(ObjectPropertyUtils.getPropertyValue(model, excludeIf))) {
167                 itemIterator.remove();
168                 continue;
169             }
170 
171             // append group's field bind by name prefix (if set) to each
172             // attribute field's binding prefix
173             if (component instanceof DataBinding) {
174                 DataBinding dataBinding = (DataBinding) component;
175 
176                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
177                     String bindByNamePrefixToSet = getFieldBindByNamePrefix();
178 
179                     if (StringUtils.isNotBlank(dataBinding.getBindingInfo().getBindByNamePrefix())) {
180                         bindByNamePrefixToSet += "." + dataBinding.getBindingInfo().getBindByNamePrefix();
181                     }
182                     dataBinding.getBindingInfo().setBindByNamePrefix(bindByNamePrefixToSet);
183                 }
184 
185                 if (StringUtils.isNotBlank(fieldBindingObjectPath) && StringUtils.isBlank(
186                         dataBinding.getBindingInfo().getBindingObjectPath())) {
187                     dataBinding.getBindingInfo().setBindingObjectPath(fieldBindingObjectPath);
188                 }
189             }
190             // set on FieldGroup's group to recursively set AttributeFields
191             else if (component instanceof FieldGroup) {
192                 FieldGroup fieldGroup = (FieldGroup) component;
193 
194                 if (fieldGroup.getGroup() != null) {
195                     if (StringUtils.isBlank(fieldGroup.getGroup().getFieldBindByNamePrefix())) {
196                         fieldGroup.getGroup().setFieldBindByNamePrefix(fieldBindByNamePrefix);
197                     }
198                     if (StringUtils.isBlank(fieldGroup.getGroup().getFieldBindingObjectPath())) {
199                         fieldGroup.getGroup().setFieldBindingObjectPath(fieldBindingObjectPath);
200                     }
201                 }
202             } else if (component instanceof Group) {
203                 Group subGroup = (Group) component;
204                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
205                     if (StringUtils.isNotBlank(subGroup.getFieldBindByNamePrefix())) {
206                         subGroup.setFieldBindByNamePrefix(
207                                 getFieldBindByNamePrefix() + "." + subGroup.getFieldBindByNamePrefix());
208                     } else {
209                         subGroup.setFieldBindByNamePrefix(getFieldBindByNamePrefix());
210                     }
211                 }
212                 if (StringUtils.isNotBlank(getFieldBindingObjectPath())) {
213                     if (StringUtils.isNotBlank(subGroup.getFieldBindingObjectPath())) {
214                         subGroup.setFieldBindingObjectPath(
215                                 getFieldBindingObjectPath() + "." + subGroup.getFieldBindingObjectPath());
216                     } else {
217                         subGroup.setFieldBindingObjectPath(getFieldBindingObjectPath());
218                     }
219                 }
220             }
221         }
222     }
223 
224     /**
225      * {@inheritDoc}
226      */
227     @Override
228     public void afterEvaluateExpression() {
229         super.afterEvaluateExpression();
230 
231         if (getReadOnly() == null) {
232             Component parent = ViewLifecycle.getPhase().getParent();
233             setReadOnly(parent == null ? null : parent.getReadOnly());
234         }
235     }
236 
237     /**
238      * Sets the section boolean to true if this group has a rendering header with text
239      *
240      * {@inheritDoc}
241      */
242     @Override
243     public void performFinalize(Object model, LifecycleElement parent) {
244         super.performFinalize(model, parent);
245 
246         if (StringUtils.isBlank(wrapperTag) && StringUtils.isNotBlank(this.getHeaderText()) && this.getHeader()
247                 .isRender()) {
248             wrapperTag = UifConstants.WrapperTags.SECTION;
249         } else if (StringUtils.isBlank(wrapperTag)) {
250             wrapperTag = UifConstants.WrapperTags.DIV;
251         }
252 
253         setNestedComponentId(getInstructionalMessage(), this.getId() + UifConstants.IdSuffixes.INSTRUCTIONAL);
254         setNestedComponentId(getHeader(), this.getId() + UifConstants.IdSuffixes.HEADER_WRAPPER);
255         setNestedComponentId(getHelp(), this.getId() + UifConstants.IdSuffixes.HELP_WRAPPER);
256 
257         if (getHelp() != null && getHelp().getHelpAction() != null) {
258             setNestedComponentId(getHelp().getHelpAction(), this.getId() + UifConstants.IdSuffixes.HELP);
259         }
260     }
261 
262     /**
263      * Helper method for setting a new ID for the nested components
264      *
265      * @param component component to adjust ID for
266      * @param newId
267      */
268     protected void setNestedComponentId(Component component, String newId) {
269         if (component != null) {
270             component.setId(newId);
271         }
272     }
273 
274     /**
275      * {@inheritDoc}
276      */
277     @Override
278     public Set<Class<? extends Component>> getSupportedComponents() {
279         Set<Class<? extends Component>> supportedComponents = new HashSet<Class<? extends Component>>();
280         supportedComponents.add(Field.class);
281         supportedComponents.add(Group.class);
282 
283         return supportedComponents;
284     }
285 
286     /**
287      * {@inheritDoc}
288      */
289     @Override
290     public String getComponentTypeName() {
291         return "group";
292     }
293 
294     /**
295      * {@inheritDoc}
296      */
297     @Override
298     @BeanTagAttribute
299     public String getFieldBindByNamePrefix() {
300         return this.fieldBindByNamePrefix;
301     }
302 
303     /**
304      * {@inheritDoc}
305      */
306     @Override
307     public void setFieldBindByNamePrefix(String fieldBindByNamePrefix) {
308         this.fieldBindByNamePrefix = fieldBindByNamePrefix;
309     }
310 
311     /**
312      * {@inheritDoc}
313      */
314     @BeanTagAttribute
315     public String getFieldBindingObjectPath() {
316         return this.fieldBindingObjectPath;
317     }
318 
319     /**
320      * {@inheritDoc}
321      */
322     @Override
323     public void setFieldBindingObjectPath(String fieldBindingObjectPath) {
324         this.fieldBindingObjectPath = fieldBindingObjectPath;
325     }
326 
327     /**
328      * {@inheritDoc}
329      */
330     @BeanTagAttribute
331     public Disclosure getDisclosure() {
332         return this.disclosure;
333     }
334 
335     /**
336      * {@inheritDoc}
337      */
338     @Override
339     public void setDisclosure(Disclosure disclosure) {
340         this.disclosure = disclosure;
341     }
342 
343     /**
344      * {@inheritDoc}
345      */
346     @BeanTagAttribute
347     public Scrollpane getScrollpane() {
348         return this.scrollpane;
349     }
350 
351     /**
352      * {@inheritDoc}
353      */
354     @Override
355     public void setScrollpane(Scrollpane scrollpane) {
356         this.scrollpane = scrollpane;
357     }
358 
359     /**
360      * {@inheritDoc}
361      */
362     @Override
363     @BeanTagAttribute
364     public List<? extends Component> getItems() {
365         if (items == Collections.EMPTY_LIST && isMutable(true)) {
366             items = new ArrayList<Component>();
367         }
368 
369         return this.items;
370     }
371 
372     /**
373      * {@inheritDoc}
374      */
375     @Override
376     public void setItems(List<? extends Component> items) {
377         if (items == null) {
378             this.items = Collections.emptyList();
379         } else if (items.contains(this)) {
380             throw new IllegalArgumentException("Attempted to add group to itself");
381         } else {
382             this.items = items;
383         }
384     }
385 
386     /**
387      * Defines the html tag that will wrap this group, if left blank, this will automatically be set
388      * by the framework to the appropriate tag (in most cases section or div)
389      *
390      * @return the html tag used to wrap this group
391      */
392     @BeanTagAttribute
393     public String getWrapperTag() {
394         return wrapperTag;
395     }
396 
397     /**
398      * @see org.kuali.rice.krad.uif.container.GroupBase#getWrapperTag()
399      */
400     public void setWrapperTag(String wrapperTag) {
401         this.wrapperTag = wrapperTag;
402     }
403 
404     /**
405      * Returns true if this group has a Disclosure widget that is currently closed and using ajax disclosure
406      *
407      * @return true if this group has a Disclosure widget that is currently closed and using ajax disclosure
408      */
409     protected boolean isAjaxDisclosureGroup() {
410         ViewModel model = (ViewModel) ViewLifecycle.getModel();
411         View view = ViewLifecycle.getView();
412 
413         ExpressionUtils.populatePropertyExpressionsFromGraph(this);
414         // Evaluate the disclosure.defaultOpen expression early so that ajax disclosure mechanisms
415         // can take its state into account when replacing items with Placeholders in ContainerBase#performInitialization
416         if (this.getDisclosure() != null && StringUtils.isNotBlank(this.getDisclosure().getPropertyExpression(
417                 UifPropertyPaths.DEFAULT_OPEN))){
418             ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
419 
420             String expression = this.getDisclosure().getPropertyExpression(UifPropertyPaths.DEFAULT_OPEN);
421             expression = expressionEvaluator.replaceBindingPrefixes(view, this, expression);
422 
423             expression = expressionEvaluator.evaluateExpressionTemplate(this.getDisclosure().getContext(), expression);
424             ObjectPropertyUtils.setPropertyValue(this.getDisclosure(), UifPropertyPaths.DEFAULT_OPEN, expression);
425         }
426 
427         // Ensure that the disclosure has the correct state before evaluate ajax-based placeholder replacement
428         if (this.getDisclosure() != null) {
429             KRADUtils.syncClientSideStateForComponent(this.getDisclosure(), model.getClientStateForSyncing());
430         }
431 
432         // This this will be replaced with a PlaceholderDisclosure group if it is not opened and the
433         // ajaxRetrievalWhenOpened option is set
434         return !this.isRetrieveViaAjax() && this.getDisclosure() != null && this.getDisclosure()
435                         .isAjaxRetrievalWhenOpened() && !this.getDisclosure().isDefaultOpen();
436     }
437 
438     /**
439      * {@inheritDoc}
440      */
441     @Override
442     public void completeValidation(ValidationTrace tracer) {
443         tracer.addBean(this);
444 
445         // Checks that no invalid items are present
446         for (int i = 0; i < getItems().size(); i++) {
447             if (getItems().get(i).getClass() == PageGroup.class || getItems().get(i).getClass()
448                     == TabNavigationGroup.class) {
449                 String currentValues[] = {"item(" + i + ").class =" + getItems().get(i).getClass()};
450                 tracer.createError("Items in Group cannot be PageGroup or NaviagtionGroup", currentValues);
451             }
452         }
453 
454         // Checks that the layout manager is set
455         if (getLayoutManager() == null) {
456             if (Validator.checkExpressions(this, "layoutManager")) {
457                 String currentValues[] = {"layoutManager = " + getLayoutManager()};
458                 tracer.createError("LayoutManager must be set", currentValues);
459             }
460         }
461 
462         super.completeValidation(tracer.getCopy());
463     }
464 
465     /**
466      * {@inheritDoc}
467      */
468     @Override
469     public boolean isRenderLoading() {
470         return disclosure != null && disclosure.isAjaxRetrievalWhenOpened() && (!disclosure.isRender() || !disclosure
471                 .isDefaultOpen());
472     }
473 
474 }