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 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.datadictionary.parse.BeanTags;
22  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
23  import org.kuali.rice.krad.datadictionary.validator.Validator;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.UifPropertyPaths;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.component.DataBinding;
28  import org.kuali.rice.krad.uif.component.DelayedCopy;
29  import org.kuali.rice.krad.uif.element.Action;
30  import org.kuali.rice.krad.uif.field.Field;
31  import org.kuali.rice.krad.uif.field.FieldGroup;
32  import org.kuali.rice.krad.uif.field.InputField;
33  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
34  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
35  import org.kuali.rice.krad.uif.util.ExpressionUtils;
36  import org.kuali.rice.krad.uif.util.LifecycleElement;
37  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
38  import org.kuali.rice.krad.uif.util.ScriptUtils;
39  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
40  import org.kuali.rice.krad.uif.view.View;
41  import org.kuali.rice.krad.uif.view.ViewModel;
42  import org.kuali.rice.krad.uif.widget.Disclosure;
43  import org.kuali.rice.krad.uif.widget.Inquiry;
44  import org.kuali.rice.krad.uif.widget.QuickFinder;
45  import org.kuali.rice.krad.uif.widget.Scrollpane;
46  import org.kuali.rice.krad.util.KRADUtils;
47  
48  import java.util.ArrayList;
49  import java.util.Arrays;
50  import java.util.Collections;
51  import java.util.HashSet;
52  import java.util.Iterator;
53  import java.util.List;
54  import java.util.Set;
55  
56  /**
57   * Container that holds a list of <code>Field</code> or other <code>Group</code>
58   * instances
59   *
60   * <p>
61   * Groups can exist at different levels of the <code>View</code>, providing
62   * conceptual groupings such as the page, section, and group. In addition, other
63   * group types can be created to add behavior like collection support
64   * </p>
65   *
66   * <p>
67   * <code>Group</code> implementation has properties for defaulting the binding
68   * information (such as the parent object path and a binding prefix) for the
69   * fields it contains. During the phase these properties (if given) are set on
70   * the fields contained in the <code>Group</code> that implement
71   * <code>DataBinding</code>, unless they have already been set on the field.
72   * </p>
73   *
74   * @author Kuali Rice Team (rice.collab@kuali.org)
75   */
76  @BeanTags({@BeanTag(name = "group", parent = "Uif-GroupBase"),
77          @BeanTag(name = "boxGroup", parent = "Uif-BoxGroupBase"),
78          @BeanTag(name = "verticalGroup", parent = "Uif-VerticalBoxGroup"),
79          @BeanTag(name = "verticalSection", parent = "Uif-VerticalBoxSection"),
80          @BeanTag(name = "verticalSubSection", parent = "Uif-VerticalBoxSubSection"),
81          @BeanTag(name = "disclosureVerticalSection", parent = "Uif-Disclosure-VerticalBoxSection"),
82          @BeanTag(name = "disclosureVerticalSubSection", parent = "Uif-Disclosure-VerticalBoxSubSection"),
83          @BeanTag(name = "horizontalGroup", parent = "Uif-HorizontalBoxGroup"),
84          @BeanTag(name = "horizontalSection", parent = "Uif-HorizontalBoxSection"),
85          @BeanTag(name = "horizontalSubSection", parent = "Uif-HorizontalBoxSubSection"),
86          @BeanTag(name = "disclosureHorizontalSection", parent = "Uif-Disclosure-HorizontalBoxSection"),
87          @BeanTag(name = "disclosureHorizontalSubSection", parent = "Uif-Disclosure-HorizontalBoxSubSection"),
88          @BeanTag(name = "grid", parent = "Uif-GridGroup"),
89          @BeanTag(name = "gridSection", parent = "Uif-GridSection"),
90          @BeanTag(name = "gridSubSection", parent = "Uif-GridSubSection"),
91          @BeanTag(name = "disclosureGridSection", parent = "Uif-Disclosure-GridSection"),
92          @BeanTag(name = "cssGrid", parent = "Uif-CssGridGroup"),
93          @BeanTag(name = "section", parent = "Uif-CssGridSection"),
94          @BeanTag(name = "subSection", parent = "Uif-CssGridSubSection"),
95          @BeanTag(name = "section1Col", parent = "Uif-CssGridSection-1FieldLabelColumn"),
96          @BeanTag(name = "section2Col", parent = "Uif-CssGridSection-2FieldLabelColumn"),
97          @BeanTag(name = "section3Col", parent = "Uif-CssGridSection-3FieldLabelColumn"),
98          @BeanTag(name = "subSection1Col", parent = "Uif-CssGridSubSection-1FieldLabelColumn"),
99          @BeanTag(name = "subSection2Col", parent = "Uif-CssGridSubSection-2FieldLabelColumn"),
100         @BeanTag(name = "subSection3Col", parent = "Uif-CssGridSubSection-3FieldLabelColumn"),
101         @BeanTag(name = "list", parent = "Uif-ListGroup"),
102         @BeanTag(name = "listSection", parent = "Uif-ListSection"),
103         @BeanTag(name = "listSubSection", parent = "Uif-ListSubSection"),
104         @BeanTag(name = "disclosureListSection", parent = "Uif-Disclosure-ListSection"),
105         @BeanTag(name = "disclosureListSubSection", parent = "Uif-Disclosure-ListSubSection"),
106         @BeanTag(name = "collectionGridItem", parent = "Uif-CollectionGridItem"),
107         @BeanTag(name = "collectionVerticalBoxItem", parent = "Uif-CollectionVerticalBoxItem"),
108         @BeanTag(name = "collectionHorizontalBoxItem", parent = "Uif-CollectionHorizontalBoxItem"),
109         @BeanTag(name = "headerUpperGroup", parent = "Uif-HeaderUpperGroup"),
110         @BeanTag(name = "headerRightGroup", parent = "Uif-HeaderRightGroup"),
111         @BeanTag(name = "headerLowerGroup", parent = "Uif-HeaderLowerGroup"),
112         @BeanTag(name = "footer", parent = "Uif-FooterBase"),
113         @BeanTag(name = "formFooter", parent = "Uif-FormFooter"),
114         @BeanTag(name = "maintenanceGrid", parent = "Uif-MaintenanceGridGroup"),
115         @BeanTag(name = "maintenanceHorizontalGroup", parent = "Uif-MaintenanceHorizontalBoxGroup"),
116         @BeanTag(name = "maintenanceVerticalGroup", parent = "Uif-MaintenanceVerticalBoxGroup"),
117         @BeanTag(name = "maintenanceGridSection", parent = "Uif-MaintenanceGridSection"),
118         @BeanTag(name = "maintenanceGridSubSection", parent = "Uif-MaintenanceGridSubSection"),
119         @BeanTag(name = "maintenanceHorizontalSection", parent = "Uif-MaintenanceHorizontalBoxSection"),
120         @BeanTag(name = "maintenanceVerticalSection", parent = "Uif-MaintenanceVerticalBoxSection"),
121         @BeanTag(name = "maintenanceHorizontalSubSection", parent = "Uif-MaintenanceHorizontalBoxSubSection"),
122         @BeanTag(name = "maintenanceVerticalSubSection", parent = "Uif-MaintenanceVerticalBoxSubSection")})
123 public class GroupBase extends ContainerBase implements Group {
124     private static final long serialVersionUID = 7953641325356535509L;
125 
126     public static enum ACTION_VALIDATION_COMPONENTS {QUICKFINDER, INQUIRY, COLLECTION}
127 
128     private String fieldBindByNamePrefix;
129     private String fieldBindingObjectPath;
130 
131     @DelayedCopy
132     private Disclosure disclosure;
133     private Scrollpane scrollpane;
134 
135     private List<? extends Component> items;
136 
137     private String wrapperTag;
138 
139     /**
140      * Default Constructor
141      */
142     public GroupBase() {
143         items = Collections.emptyList();
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     @Override
150     public void performInitialization(Object model) {
151         if (isAjaxDisclosureGroup()) {
152             this.setItems(new ArrayList<Component>());
153         }
154 
155         super.performInitialization(model);
156 
157         Iterator<? extends Component> itemIterator = getItems().iterator();
158         while (itemIterator.hasNext()) {
159             Component component = itemIterator.next();
160 
161             if (component == null) {
162                 continue;
163             }
164 
165             String excludeUnless = component.getExcludeUnless();
166             if (StringUtils.isNotBlank(excludeUnless) &&
167                     !Boolean.TRUE.equals(ObjectPropertyUtils.getPropertyValue(model, excludeUnless))) {
168                 itemIterator.remove();
169                 continue;
170             }
171 
172             String excludeIf = component.getExcludeIf();
173             if (StringUtils.isNotBlank(excludeIf) &&
174                     Boolean.TRUE.equals(ObjectPropertyUtils.getPropertyValue(model, excludeIf))) {
175                 itemIterator.remove();
176                 continue;
177             }
178 
179             // append group's field bind by name prefix (if set) to each
180             // attribute field's binding prefix
181             if (component instanceof DataBinding) {
182                 DataBinding dataBinding = (DataBinding) component;
183 
184                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
185                     String bindByNamePrefixToSet = getFieldBindByNamePrefix();
186 
187                     if (StringUtils.isNotBlank(dataBinding.getBindingInfo().getBindByNamePrefix())) {
188                         bindByNamePrefixToSet += "." + dataBinding.getBindingInfo().getBindByNamePrefix();
189                     }
190                     dataBinding.getBindingInfo().setBindByNamePrefix(bindByNamePrefixToSet);
191                 }
192 
193                 if (StringUtils.isNotBlank(fieldBindingObjectPath) && StringUtils.isBlank(
194                         dataBinding.getBindingInfo().getBindingObjectPath())) {
195                     dataBinding.getBindingInfo().setBindingObjectPath(fieldBindingObjectPath);
196                 }
197             }
198             // set on FieldGroup's group to recursively set AttributeFields
199             else if (component instanceof FieldGroup) {
200                 FieldGroup fieldGroup = (FieldGroup) component;
201 
202                 if (fieldGroup.getGroup() != null) {
203                     if (StringUtils.isBlank(fieldGroup.getGroup().getFieldBindByNamePrefix())) {
204                         fieldGroup.getGroup().setFieldBindByNamePrefix(fieldBindByNamePrefix);
205                     }
206                     if (StringUtils.isBlank(fieldGroup.getGroup().getFieldBindingObjectPath())) {
207                         fieldGroup.getGroup().setFieldBindingObjectPath(fieldBindingObjectPath);
208                     }
209                 }
210             } else if (component instanceof Group) {
211                 Group subGroup = (Group) component;
212                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
213                     if (StringUtils.isNotBlank(subGroup.getFieldBindByNamePrefix())) {
214                         subGroup.setFieldBindByNamePrefix(
215                                 getFieldBindByNamePrefix() + "." + subGroup.getFieldBindByNamePrefix());
216                     } else {
217                         subGroup.setFieldBindByNamePrefix(getFieldBindByNamePrefix());
218                     }
219                 }
220                 if (StringUtils.isNotBlank(getFieldBindingObjectPath())) {
221                     if (StringUtils.isNotBlank(subGroup.getFieldBindingObjectPath())) {
222                         subGroup.setFieldBindingObjectPath(
223                                 getFieldBindingObjectPath() + "." + subGroup.getFieldBindingObjectPath());
224                     } else {
225                         subGroup.setFieldBindingObjectPath(getFieldBindingObjectPath());
226                     }
227                 }
228             }
229         }
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     @Override
236     public void afterEvaluateExpression() {
237         super.afterEvaluateExpression();
238 
239         if (getReadOnly() == null) {
240             Component parent = ViewLifecycle.getPhase().getParent();
241             setReadOnly(parent == null ? null : parent.getReadOnly());
242         }
243     }
244 
245     /**
246      * Sets the section boolean to true if this group has a rendering header with text
247      *
248      * {@inheritDoc}
249      */
250     @Override
251     public void performFinalize(Object model, LifecycleElement parent) {
252         super.performFinalize(model, parent);
253 
254         if (StringUtils.isBlank(wrapperTag) && StringUtils.isNotBlank(this.getHeaderText()) && this.getHeader()
255                 .isRender()) {
256             wrapperTag = UifConstants.WrapperTags.SECTION;
257         } else if (StringUtils.isBlank(wrapperTag)) {
258             wrapperTag = UifConstants.WrapperTags.DIV;
259         }
260 
261         setNestedComponentId(getInstructionalMessage(), this.getId() + UifConstants.IdSuffixes.INSTRUCTIONAL);
262         setNestedComponentId(getHeader(), this.getId() + UifConstants.IdSuffixes.HEADER_WRAPPER);
263         setNestedComponentId(getHelp(), this.getId() + UifConstants.IdSuffixes.HELP_WRAPPER);
264 
265         if (getHelp() != null && getHelp().getHelpAction() != null) {
266             setNestedComponentId(getHelp().getHelpAction(), this.getId() + UifConstants.IdSuffixes.HELP);
267         }
268 
269         // set up action validation scripts to avoid bind errors
270         setupValidationScripts(ACTION_VALIDATION_COMPONENTS.QUICKFINDER, ACTION_VALIDATION_COMPONENTS.INQUIRY);
271 
272         // set the fields in the group to omit their data on form post according to its own value
273         List<Field> fields = ViewLifecycleUtils.getElementsOfTypeDeep(getItems(), Field.class);
274         for (Field field : fields) {
275             field.setOmitFromFormPost(isOmitFromFormPost());
276         }
277     }
278 
279     /**
280      * Helper method to set the validation action scripts for widgets (at the very least, quickfinders, and currently,
281      * at the most, inquiries also).
282      *
283      * @param componentsToValidate the list of components with actions to validate
284      */
285     protected void setupValidationScripts(ACTION_VALIDATION_COMPONENTS... componentsToValidate) {
286         List<ACTION_VALIDATION_COMPONENTS> componentsList = Arrays.asList(componentsToValidate);
287         List<InputField> inputFieldsWithActionsToValidate = new ArrayList<InputField>();
288         List<InputField> allInputFields = new ArrayList<InputField>();
289 
290         // get all the components with quickfinder and inquiry (Note: for now only these 2 widgets are required
291         // to be supported but in future this could include possibly others if needed, and therefore the enum)
292         for (Component component : getItems()) {
293             if (component instanceof InputField) {
294                 InputField inputField = (InputField) component;
295                 QuickFinder quickFinder = inputField.getQuickfinder();
296                 Inquiry inquiry = inputField.getInquiry();
297                 if ((quickFinder != null && componentsList.contains(ACTION_VALIDATION_COMPONENTS.QUICKFINDER)) || (
298                         inquiry != null && componentsList.contains(ACTION_VALIDATION_COMPONENTS.INQUIRY))) {
299                     inputFieldsWithActionsToValidate.add(inputField);
300                 }
301                 allInputFields.add(inputField);
302             }
303         }
304 
305         // for all input fields with widgets we want to set their action script to validate the other
306         // fields in this group that the widgets are effected by
307         for (InputField widgetInputField : inputFieldsWithActionsToValidate) {
308             QuickFinder quickFinder = widgetInputField.getQuickfinder();
309             Inquiry inquiry = widgetInputField.getInquiry();
310             String script = buildInputFieldValidationActionScript(allInputFields, Arrays.asList(
311                     widgetInputField.getId()));
312 
313             // quickfinders
314             if (componentsList.contains(ACTION_VALIDATION_COMPONENTS.QUICKFINDER) && quickFinder != null) {
315                 Action quickFinderAction = quickFinder.getQuickfinderAction();
316                 script = ScriptUtils.appendScript(script, quickFinderAction.getActionScript());
317                 quickFinderAction.setActionScript(script);
318             }
319 
320             // inquiries
321             if (componentsList.contains(ACTION_VALIDATION_COMPONENTS.INQUIRY) && inquiry != null) {
322                 Action directInquiryAction = inquiry.getDirectInquiryAction();
323                 script = ScriptUtils.appendScript(script, directInquiryAction.getActionScript());
324                 directInquiryAction.setActionScript(script);
325             }
326         }
327     }
328 
329     /**
330      * Helper method to build action script for input fields with actions that depend on the validation of
331      * other input fields in the same group.
332      *
333      * @param allInputFields all other input fields that might have validation constraints
334      * @param excludedFields fields that shouldn't be part of the validation script
335      * @return the validation action script
336      */
337     protected String buildInputFieldValidationActionScript(List<InputField> allInputFields,
338             List<String> excludedFields) {
339         List<String> controlsToValidate = new ArrayList<String>();
340 
341         for (InputField inputField : allInputFields) {
342             if ((excludedFields == null || !excludedFields.contains(inputField.getId())) && validateInputField(
343                     inputField)) {
344                 controlsToValidate.add(inputField.getId() + UifConstants.IdSuffixes.CONTROL);
345 
346                 // in cases of collection groups we also want to account for add line
347                 if (this instanceof CollectionGroup) {
348                     controlsToValidate.add(
349                             inputField.getId() + UifConstants.IdSuffixes.ADD_LINE + UifConstants.IdSuffixes.CONTROL);
350                 }
351             }
352         }
353 
354         // set the action scripts and set them on quickfinders and/or inquiries
355         String script = "";
356         if (!controlsToValidate.isEmpty()) {
357             script = "var control;var allValid=true;";
358             for (String controlToValidate : controlsToValidate) {
359                 script += "control=jQuery('#" + controlToValidate
360                         + "');if(jQuery(control).val()){allValid=allValid&&validateFieldValue(control);}";
361             }
362             script += "if(allValid == 0){return;}control = null;";
363         }
364         return script;
365     }
366 
367     /**
368      * Helper method to determine whether the given input field needs validated or not.
369      *
370      * @param inputField the field to check for validation
371      * @return whether validation should be done
372      */
373     protected boolean validateInputField(InputField inputField) {
374         if (inputField.getValidCharactersConstraint() != null
375                 && inputField.getValidCharactersConstraint().getApplyClientSide() != null
376                 && inputField.getValidCharactersConstraint().getApplyClientSide() == Boolean.TRUE) {
377             return true;
378         }
379         return false;
380     }
381 
382     /**
383      * Helper method for setting a new ID for the nested components
384      *
385      * @param component component to adjust ID for
386      * @param newId
387      */
388     protected void setNestedComponentId(Component component, String newId) {
389         if (component != null) {
390             component.setId(newId);
391         }
392     }
393 
394     /**
395      * {@inheritDoc}
396      */
397     @Override
398     public Set<Class<? extends Component>> getSupportedComponents() {
399         Set<Class<? extends Component>> supportedComponents = new HashSet<Class<? extends Component>>();
400         supportedComponents.add(Field.class);
401         supportedComponents.add(Group.class);
402 
403         return supportedComponents;
404     }
405 
406     /**
407      * {@inheritDoc}
408      */
409     @Override
410     public String getComponentTypeName() {
411         return "group";
412     }
413 
414     /**
415      * {@inheritDoc}
416      */
417     @Override
418     @BeanTagAttribute
419     public String getFieldBindByNamePrefix() {
420         return this.fieldBindByNamePrefix;
421     }
422 
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public void setFieldBindByNamePrefix(String fieldBindByNamePrefix) {
428         this.fieldBindByNamePrefix = fieldBindByNamePrefix;
429     }
430 
431     /**
432      * {@inheritDoc}
433      */
434     @BeanTagAttribute
435     public String getFieldBindingObjectPath() {
436         return this.fieldBindingObjectPath;
437     }
438 
439     /**
440      * {@inheritDoc}
441      */
442     @Override
443     public void setFieldBindingObjectPath(String fieldBindingObjectPath) {
444         this.fieldBindingObjectPath = fieldBindingObjectPath;
445     }
446 
447     /**
448      * {@inheritDoc}
449      */
450     @BeanTagAttribute
451     public Disclosure getDisclosure() {
452         return this.disclosure;
453     }
454 
455     /**
456      * {@inheritDoc}
457      */
458     @Override
459     public void setDisclosure(Disclosure disclosure) {
460         this.disclosure = disclosure;
461     }
462 
463     /**
464      * {@inheritDoc}
465      */
466     @BeanTagAttribute
467     public Scrollpane getScrollpane() {
468         return this.scrollpane;
469     }
470 
471     /**
472      * {@inheritDoc}
473      */
474     @Override
475     public void setScrollpane(Scrollpane scrollpane) {
476         this.scrollpane = scrollpane;
477     }
478 
479     /**
480      * {@inheritDoc}
481      */
482     @Override
483     @BeanTagAttribute
484     public List<? extends Component> getItems() {
485         if (items == Collections.EMPTY_LIST && isMutable(true)) {
486             items = new ArrayList<Component>();
487         }
488 
489         return this.items;
490     }
491 
492     /**
493      * {@inheritDoc}
494      */
495     @Override
496     public void setItems(List<? extends Component> items) {
497         if (items == null) {
498             this.items = Collections.emptyList();
499         } else if (items.contains(this)) {
500             throw new IllegalArgumentException("Attempted to add group to itself");
501         } else {
502             this.items = items;
503         }
504     }
505 
506     /**
507      * Defines the html tag that will wrap this group, if left blank, this will automatically be set
508      * by the framework to the appropriate tag (in most cases section or div)
509      *
510      * @return the html tag used to wrap this group
511      */
512     @BeanTagAttribute
513     public String getWrapperTag() {
514         return wrapperTag;
515     }
516 
517     /**
518      * @see org.kuali.rice.krad.uif.container.GroupBase#getWrapperTag()
519      */
520     public void setWrapperTag(String wrapperTag) {
521         this.wrapperTag = wrapperTag;
522     }
523 
524     /**
525      * Returns true if this group has a Disclosure widget that is currently closed and using ajax disclosure
526      *
527      * @return true if this group has a Disclosure widget that is currently closed and using ajax disclosure
528      */
529     protected boolean isAjaxDisclosureGroup() {
530         ViewModel model = (ViewModel) ViewLifecycle.getModel();
531         View view = ViewLifecycle.getView();
532 
533         ExpressionUtils.populatePropertyExpressionsFromGraph(this);
534         // Evaluate the disclosure.defaultOpen expression early so that ajax disclosure mechanisms
535         // can take its state into account when replacing items with Placeholders in ContainerBase#performInitialization
536         if (this.getDisclosure() != null && StringUtils.isNotBlank(this.getDisclosure().getPropertyExpression(
537                 UifPropertyPaths.DEFAULT_OPEN))){
538             ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
539 
540             String expression = this.getDisclosure().getPropertyExpression(UifPropertyPaths.DEFAULT_OPEN);
541             expression = expressionEvaluator.replaceBindingPrefixes(view, this, expression);
542 
543             expression = expressionEvaluator.evaluateExpressionTemplate(this.getDisclosure().getContext(), expression);
544             ObjectPropertyUtils.setPropertyValue(this.getDisclosure(), UifPropertyPaths.DEFAULT_OPEN, expression);
545         }
546 
547         // Ensure that the disclosure has the correct state before evaluate ajax-based placeholder replacement
548         if (this.getDisclosure() != null) {
549             KRADUtils.syncClientSideStateForComponent(this.getDisclosure(), model.getClientStateForSyncing());
550         }
551 
552         // This this will be replaced with a PlaceholderDisclosure group if it is not opened and the
553         // ajaxRetrievalWhenOpened option is set
554         return !this.isRetrieveViaAjax() && this.getDisclosure() != null && this.getDisclosure()
555                         .isAjaxRetrievalWhenOpened() && !this.getDisclosure().isDefaultOpen();
556     }
557 
558     /**
559      * {@inheritDoc}
560      */
561     @Override
562     public void completeValidation(ValidationTrace tracer) {
563         tracer.addBean(this);
564 
565         // Checks that no invalid items are present
566         for (int i = 0; i < getItems().size(); i++) {
567             if (getItems().get(i).getClass() == PageGroup.class || getItems().get(i).getClass()
568                     == TabNavigationGroup.class) {
569                 String currentValues[] = {"item(" + i + ").class =" + getItems().get(i).getClass()};
570                 tracer.createError("Items in Group cannot be PageGroup or NaviagtionGroup", currentValues);
571             }
572         }
573 
574         // Checks that the layout manager is set
575         if (getLayoutManager() == null) {
576             if (Validator.checkExpressions(this, "layoutManager")) {
577                 String currentValues[] = {"layoutManager = " + getLayoutManager()};
578                 tracer.createError("LayoutManager must be set", currentValues);
579             }
580         }
581 
582         super.completeValidation(tracer.getCopy());
583     }
584 
585     /**
586      * {@inheritDoc}
587      */
588     @Override
589     public boolean isRenderLoading() {
590         return disclosure != null && disclosure.isAjaxRetrievalWhenOpened() && (!disclosure.isRender() || !disclosure
591                 .isDefaultOpen());
592     }
593 }