001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.container;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.datadictionary.parse.BeanTag;
020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021import org.kuali.rice.krad.datadictionary.parse.BeanTags;
022import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
023import org.kuali.rice.krad.datadictionary.validator.Validator;
024import org.kuali.rice.krad.uif.UifConstants;
025import org.kuali.rice.krad.uif.UifPropertyPaths;
026import org.kuali.rice.krad.uif.component.Component;
027import org.kuali.rice.krad.uif.component.DataBinding;
028import org.kuali.rice.krad.uif.component.DelayedCopy;
029import org.kuali.rice.krad.uif.element.Action;
030import org.kuali.rice.krad.uif.field.Field;
031import org.kuali.rice.krad.uif.field.FieldGroup;
032import org.kuali.rice.krad.uif.field.InputField;
033import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
034import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
035import org.kuali.rice.krad.uif.util.ExpressionUtils;
036import org.kuali.rice.krad.uif.util.LifecycleElement;
037import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
038import org.kuali.rice.krad.uif.util.ScriptUtils;
039import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
040import org.kuali.rice.krad.uif.view.View;
041import org.kuali.rice.krad.uif.view.ViewModel;
042import org.kuali.rice.krad.uif.widget.Disclosure;
043import org.kuali.rice.krad.uif.widget.Inquiry;
044import org.kuali.rice.krad.uif.widget.QuickFinder;
045import org.kuali.rice.krad.uif.widget.Scrollpane;
046import org.kuali.rice.krad.util.KRADUtils;
047
048import java.util.ArrayList;
049import java.util.Arrays;
050import java.util.Collections;
051import java.util.HashSet;
052import java.util.Iterator;
053import java.util.List;
054import java.util.Set;
055
056/**
057 * Container that holds a list of <code>Field</code> or other <code>Group</code>
058 * instances
059 *
060 * <p>
061 * Groups can exist at different levels of the <code>View</code>, providing
062 * conceptual groupings such as the page, section, and group. In addition, other
063 * group types can be created to add behavior like collection support
064 * </p>
065 *
066 * <p>
067 * <code>Group</code> implementation has properties for defaulting the binding
068 * information (such as the parent object path and a binding prefix) for the
069 * fields it contains. During the phase these properties (if given) are set on
070 * the fields contained in the <code>Group</code> that implement
071 * <code>DataBinding</code>, unless they have already been set on the field.
072 * </p>
073 *
074 * @author Kuali Rice Team (rice.collab@kuali.org)
075 */
076@BeanTags({@BeanTag(name = "group", parent = "Uif-GroupBase"),
077        @BeanTag(name = "boxGroup", parent = "Uif-BoxGroupBase"),
078        @BeanTag(name = "verticalGroup", parent = "Uif-VerticalBoxGroup"),
079        @BeanTag(name = "verticalSection", parent = "Uif-VerticalBoxSection"),
080        @BeanTag(name = "verticalSubSection", parent = "Uif-VerticalBoxSubSection"),
081        @BeanTag(name = "disclosureVerticalSection", parent = "Uif-Disclosure-VerticalBoxSection"),
082        @BeanTag(name = "disclosureVerticalSubSection", parent = "Uif-Disclosure-VerticalBoxSubSection"),
083        @BeanTag(name = "horizontalGroup", parent = "Uif-HorizontalBoxGroup"),
084        @BeanTag(name = "horizontalSection", parent = "Uif-HorizontalBoxSection"),
085        @BeanTag(name = "horizontalSubSection", parent = "Uif-HorizontalBoxSubSection"),
086        @BeanTag(name = "disclosureHorizontalSection", parent = "Uif-Disclosure-HorizontalBoxSection"),
087        @BeanTag(name = "disclosureHorizontalSubSection", parent = "Uif-Disclosure-HorizontalBoxSubSection"),
088        @BeanTag(name = "grid", parent = "Uif-GridGroup"),
089        @BeanTag(name = "gridSection", parent = "Uif-GridSection"),
090        @BeanTag(name = "gridSubSection", parent = "Uif-GridSubSection"),
091        @BeanTag(name = "disclosureGridSection", parent = "Uif-Disclosure-GridSection"),
092        @BeanTag(name = "cssGrid", parent = "Uif-CssGridGroup"),
093        @BeanTag(name = "section", parent = "Uif-CssGridSection"),
094        @BeanTag(name = "subSection", parent = "Uif-CssGridSubSection"),
095        @BeanTag(name = "section1Col", parent = "Uif-CssGridSection-1FieldLabelColumn"),
096        @BeanTag(name = "section2Col", parent = "Uif-CssGridSection-2FieldLabelColumn"),
097        @BeanTag(name = "section3Col", parent = "Uif-CssGridSection-3FieldLabelColumn"),
098        @BeanTag(name = "subSection1Col", parent = "Uif-CssGridSubSection-1FieldLabelColumn"),
099        @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")})
123public 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        if (isOmitFromFormPost()) {
279            this.addDataAttribute(UifConstants.DataAttributes.OMIT_GROUP_FROM_POST, "true");
280        }
281    }
282
283    /**
284     * Helper method to set the validation action scripts for widgets (at the very least, quickfinders, and currently,
285     * at the most, inquiries also).
286     *
287     * @param componentsToValidate the list of components with actions to validate
288     */
289    protected void setupValidationScripts(ACTION_VALIDATION_COMPONENTS... componentsToValidate) {
290        List<ACTION_VALIDATION_COMPONENTS> componentsList = Arrays.asList(componentsToValidate);
291        List<InputField> inputFieldsWithActionsToValidate = new ArrayList<InputField>();
292        List<InputField> allInputFields = new ArrayList<InputField>();
293
294        // get all the components with quickfinder and inquiry (Note: for now only these 2 widgets are required
295        // to be supported but in future this could include possibly others if needed, and therefore the enum)
296        for (Component component : getItems()) {
297            if (component instanceof InputField) {
298                InputField inputField = (InputField) component;
299                QuickFinder quickFinder = inputField.getQuickfinder();
300                Inquiry inquiry = inputField.getInquiry();
301                if ((quickFinder != null && componentsList.contains(ACTION_VALIDATION_COMPONENTS.QUICKFINDER)) || (
302                        inquiry != null && componentsList.contains(ACTION_VALIDATION_COMPONENTS.INQUIRY))) {
303                    inputFieldsWithActionsToValidate.add(inputField);
304                }
305                allInputFields.add(inputField);
306            }
307        }
308
309        // for all input fields with widgets we want to set their action script to validate the other
310        // fields in this group that the widgets are effected by
311        for (InputField widgetInputField : inputFieldsWithActionsToValidate) {
312            QuickFinder quickFinder = widgetInputField.getQuickfinder();
313            Inquiry inquiry = widgetInputField.getInquiry();
314            String script = buildInputFieldValidationActionScript(allInputFields, Arrays.asList(
315                    widgetInputField.getId()));
316
317            // quickfinders
318            if (componentsList.contains(ACTION_VALIDATION_COMPONENTS.QUICKFINDER) && quickFinder != null) {
319                Action quickFinderAction = quickFinder.getQuickfinderAction();
320                script = ScriptUtils.appendScript(script, quickFinderAction.getActionScript());
321                quickFinderAction.setActionScript(script);
322            }
323
324            // inquiries
325            if (componentsList.contains(ACTION_VALIDATION_COMPONENTS.INQUIRY) && inquiry != null) {
326                Action directInquiryAction = inquiry.getDirectInquiryAction();
327                script = ScriptUtils.appendScript(script, directInquiryAction.getActionScript());
328                directInquiryAction.setActionScript(script);
329            }
330        }
331    }
332
333    /**
334     * Helper method to build action script for input fields with actions that depend on the validation of
335     * other input fields in the same group.
336     *
337     * @param allInputFields all other input fields that might have validation constraints
338     * @param excludedFields fields that shouldn't be part of the validation script
339     * @return the validation action script
340     */
341    protected String buildInputFieldValidationActionScript(List<InputField> allInputFields,
342            List<String> excludedFields) {
343        List<String> controlsToValidate = new ArrayList<String>();
344
345        for (InputField inputField : allInputFields) {
346            if ((excludedFields == null || !excludedFields.contains(inputField.getId())) && validateInputField(
347                    inputField)) {
348                controlsToValidate.add(inputField.getId() + UifConstants.IdSuffixes.CONTROL);
349
350                // in cases of collection groups we also want to account for add line
351                if (this instanceof CollectionGroup) {
352                    controlsToValidate.add(
353                            inputField.getId() + UifConstants.IdSuffixes.ADD_LINE + UifConstants.IdSuffixes.CONTROL);
354                }
355            }
356        }
357
358        // set the action scripts and set them on quickfinders and/or inquiries
359        String script = "";
360        if (!controlsToValidate.isEmpty()) {
361            script = "var control;var allValid=true;";
362            for (String controlToValidate : controlsToValidate) {
363                script += "control=jQuery('#" + controlToValidate
364                        + "');if(jQuery(control).val()){allValid=allValid&&validateFieldValue(control);}";
365            }
366            script += "if(allValid == 0){return;}control = null;";
367        }
368        return script;
369    }
370
371    /**
372     * Helper method to determine whether the given input field needs validated or not.
373     *
374     * @param inputField the field to check for validation
375     * @return whether validation should be done
376     */
377    protected boolean validateInputField(InputField inputField) {
378        if (inputField.getValidCharactersConstraint() != null
379                && inputField.getValidCharactersConstraint().getApplyClientSide() != null
380                && inputField.getValidCharactersConstraint().getApplyClientSide() == Boolean.TRUE) {
381            return true;
382        }
383        return false;
384    }
385
386    /**
387     * Helper method for setting a new ID for the nested components
388     *
389     * @param component component to adjust ID for
390     * @param newId
391     */
392    protected void setNestedComponentId(Component component, String newId) {
393        if (component != null) {
394            component.setId(newId);
395        }
396    }
397
398    /**
399     * {@inheritDoc}
400     */
401    @Override
402    public Set<Class<? extends Component>> getSupportedComponents() {
403        Set<Class<? extends Component>> supportedComponents = new HashSet<Class<? extends Component>>();
404        supportedComponents.add(Field.class);
405        supportedComponents.add(Group.class);
406
407        return supportedComponents;
408    }
409
410    /**
411     * {@inheritDoc}
412     */
413    @Override
414    public String getComponentTypeName() {
415        return "group";
416    }
417
418    /**
419     * {@inheritDoc}
420     */
421    @Override
422    @BeanTagAttribute
423    public String getFieldBindByNamePrefix() {
424        return this.fieldBindByNamePrefix;
425    }
426
427    /**
428     * {@inheritDoc}
429     */
430    @Override
431    public void setFieldBindByNamePrefix(String fieldBindByNamePrefix) {
432        this.fieldBindByNamePrefix = fieldBindByNamePrefix;
433    }
434
435    /**
436     * {@inheritDoc}
437     */
438    @BeanTagAttribute
439    public String getFieldBindingObjectPath() {
440        return this.fieldBindingObjectPath;
441    }
442
443    /**
444     * {@inheritDoc}
445     */
446    @Override
447    public void setFieldBindingObjectPath(String fieldBindingObjectPath) {
448        this.fieldBindingObjectPath = fieldBindingObjectPath;
449    }
450
451    /**
452     * {@inheritDoc}
453     */
454    @BeanTagAttribute
455    public Disclosure getDisclosure() {
456        return this.disclosure;
457    }
458
459    /**
460     * {@inheritDoc}
461     */
462    @Override
463    public void setDisclosure(Disclosure disclosure) {
464        this.disclosure = disclosure;
465    }
466
467    /**
468     * {@inheritDoc}
469     */
470    @BeanTagAttribute
471    public Scrollpane getScrollpane() {
472        return this.scrollpane;
473    }
474
475    /**
476     * {@inheritDoc}
477     */
478    @Override
479    public void setScrollpane(Scrollpane scrollpane) {
480        this.scrollpane = scrollpane;
481    }
482
483    /**
484     * {@inheritDoc}
485     */
486    @Override
487    @BeanTagAttribute
488    public List<? extends Component> getItems() {
489        if (items == Collections.EMPTY_LIST && isMutable(true)) {
490            items = new ArrayList<Component>();
491        }
492
493        return this.items;
494    }
495
496    /**
497     * {@inheritDoc}
498     */
499    @Override
500    public void setItems(List<? extends Component> items) {
501        if (items == null) {
502            this.items = Collections.emptyList();
503        } else if (items.contains(this)) {
504            throw new IllegalArgumentException("Attempted to add group to itself");
505        } else {
506            this.items = items;
507        }
508    }
509
510    /**
511     * Defines the html tag that will wrap this group, if left blank, this will automatically be set
512     * by the framework to the appropriate tag (in most cases section or div)
513     *
514     * @return the html tag used to wrap this group
515     */
516    @BeanTagAttribute
517    public String getWrapperTag() {
518        return wrapperTag;
519    }
520
521    /**
522     * @see org.kuali.rice.krad.uif.container.GroupBase#getWrapperTag()
523     */
524    public void setWrapperTag(String wrapperTag) {
525        this.wrapperTag = wrapperTag;
526    }
527
528    /**
529     * Returns true if this group has a Disclosure widget that is currently closed and using ajax disclosure
530     *
531     * @return true if this group has a Disclosure widget that is currently closed and using ajax disclosure
532     */
533    protected boolean isAjaxDisclosureGroup() {
534        ViewModel model = (ViewModel) ViewLifecycle.getModel();
535        View view = ViewLifecycle.getView();
536
537        ExpressionUtils.populatePropertyExpressionsFromGraph(this);
538        // Evaluate the disclosure.defaultOpen expression early so that ajax disclosure mechanisms
539        // can take its state into account when replacing items with Placeholders in ContainerBase#performInitialization
540        if (this.getDisclosure() != null && StringUtils.isNotBlank(this.getDisclosure().getPropertyExpression(
541                UifPropertyPaths.DEFAULT_OPEN))){
542            ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
543
544            String expression = this.getDisclosure().getPropertyExpression(UifPropertyPaths.DEFAULT_OPEN);
545            expression = expressionEvaluator.replaceBindingPrefixes(view, this, expression);
546
547            expression = expressionEvaluator.evaluateExpressionTemplate(this.getDisclosure().getContext(), expression);
548            ObjectPropertyUtils.setPropertyValue(this.getDisclosure(), UifPropertyPaths.DEFAULT_OPEN, expression);
549        }
550
551        // Ensure that the disclosure has the correct state before evaluate ajax-based placeholder replacement
552        if (this.getDisclosure() != null) {
553            KRADUtils.syncClientSideStateForComponent(this.getDisclosure(), model.getClientStateForSyncing());
554        }
555
556        // This this will be replaced with a PlaceholderDisclosure group if it is not opened and the
557        // ajaxRetrievalWhenOpened option is set
558        return !this.isRetrieveViaAjax() && this.getDisclosure() != null && this.getDisclosure()
559                        .isAjaxRetrievalWhenOpened() && !this.getDisclosure().isDefaultOpen();
560    }
561
562    /**
563     * {@inheritDoc}
564     */
565    @Override
566    public void completeValidation(ValidationTrace tracer) {
567        tracer.addBean(this);
568
569        // Checks that no invalid items are present
570        for (int i = 0; i < getItems().size(); i++) {
571            if (getItems().get(i).getClass() == PageGroup.class || getItems().get(i).getClass()
572                    == TabNavigationGroup.class) {
573                String currentValues[] = {"item(" + i + ").class =" + getItems().get(i).getClass()};
574                tracer.createError("Items in Group cannot be PageGroup or NaviagtionGroup", currentValues);
575            }
576        }
577
578        // Checks that the layout manager is set
579        if (getLayoutManager() == null) {
580            if (Validator.checkExpressions(this, "layoutManager")) {
581                String currentValues[] = {"layoutManager = " + getLayoutManager()};
582                tracer.createError("LayoutManager must be set", currentValues);
583            }
584        }
585
586        super.completeValidation(tracer.getCopy());
587    }
588
589    /**
590     * {@inheritDoc}
591     */
592    @Override
593    public boolean isRenderLoading() {
594        return disclosure != null && disclosure.isAjaxRetrievalWhenOpened() && (!disclosure.isRender() || !disclosure
595                .isDefaultOpen());
596    }
597}