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.view;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.CoreApiServiceLocator;
20  import org.kuali.rice.krad.datadictionary.DataDictionary;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
22  import org.kuali.rice.krad.datadictionary.state.StateMapping;
23  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
24  import org.kuali.rice.krad.datadictionary.validator.Validator;
25  import org.kuali.rice.krad.lookup.LookupView;
26  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
27  import org.kuali.rice.krad.uif.UifConstants;
28  import org.kuali.rice.krad.uif.UifConstants.ViewStatus;
29  import org.kuali.rice.krad.uif.UifConstants.ViewType;
30  import org.kuali.rice.krad.uif.UifParameters;
31  import org.kuali.rice.krad.uif.UifPropertyPaths;
32  import org.kuali.rice.krad.uif.component.BindingInfo;
33  import org.kuali.rice.krad.uif.component.Component;
34  import org.kuali.rice.krad.uif.component.DelayedCopy;
35  import org.kuali.rice.krad.uif.component.ReferenceCopy;
36  import org.kuali.rice.krad.uif.component.RequestParameter;
37  import org.kuali.rice.krad.uif.container.ContainerBase;
38  import org.kuali.rice.krad.uif.container.Group;
39  import org.kuali.rice.krad.uif.container.PageGroup;
40  import org.kuali.rice.krad.uif.element.BreadcrumbItem;
41  import org.kuali.rice.krad.uif.element.BreadcrumbOptions;
42  import org.kuali.rice.krad.uif.element.HeadLink;
43  import org.kuali.rice.krad.uif.element.Header;
44  import org.kuali.rice.krad.uif.element.MetaTag;
45  import org.kuali.rice.krad.uif.element.ViewHeader;
46  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
47  import org.kuali.rice.krad.uif.lifecycle.ViewLifecyclePhase;
48  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
49  import org.kuali.rice.krad.uif.service.ViewHelperService;
50  import org.kuali.rice.krad.uif.util.ClientValidationUtils;
51  import org.kuali.rice.krad.uif.util.ComponentFactory;
52  import org.kuali.rice.krad.uif.util.CopyUtils;
53  import org.kuali.rice.krad.uif.util.LifecycleAwareList;
54  import org.kuali.rice.krad.uif.util.LifecycleAwareMap;
55  import org.kuali.rice.krad.uif.util.LifecycleElement;
56  import org.kuali.rice.krad.uif.util.ParentLocation;
57  import org.kuali.rice.krad.uif.util.ScriptUtils;
58  import org.kuali.rice.krad.uif.widget.BlockUI;
59  import org.kuali.rice.krad.uif.widget.Breadcrumbs;
60  import org.kuali.rice.krad.uif.widget.Growls;
61  import org.kuali.rice.krad.uif.widget.Help;
62  import org.kuali.rice.krad.util.GlobalVariables;
63  import org.kuali.rice.krad.util.KRADConstants;
64  import org.kuali.rice.krad.util.KRADUtils;
65  import org.kuali.rice.krad.web.form.UifFormBase;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  
69  import java.util.ArrayList;
70  import java.util.Collections;
71  import java.util.HashMap;
72  import java.util.HashSet;
73  import java.util.List;
74  import java.util.Map;
75  import java.util.Set;
76  
77  /**
78   * Root of the component tree which encompasses a set of related
79   * GroupContainer instances tied together with a common page layout
80   * and navigation.
81   *
82   * <p>
83   * The View component ties together all the components and
84   * configuration of the User Interface for a piece of functionality. In Rice
85   * applications the view is typically associated with a Document
86   * instance.
87   * </p>
88   *
89   * <p>
90   * The view template lays out the common header, footer, and navigation for the
91   * related pages. In addition the view renders the HTML head element bringing in
92   * common script files and style sheets, along with optionally rendering a form
93   * element for pages that need to post data back to the server.
94   * </p>
95   *
96   * <p>
97   * Configuration of UIF features such as model validation is also done through
98   * the View
99   * </p>
100  *
101  * @author Kuali Rice Team (rice.collab@kuali.org)
102  */
103 public class View extends ContainerBase {
104     private static final long serialVersionUID = -1220009725554576953L;
105     private static final Logger LOG = LoggerFactory.getLogger(ContainerBase.class);
106 
107     private String namespaceCode;
108     private String viewName;
109     private ViewTheme theme;
110 
111     private String stateObjectBindingPath;
112     private StateMapping stateMapping;
113 
114     // view header setting
115     private boolean unifiedHeader;
116 
117     // additional view group(s)
118     private Group topGroup;
119 
120     // application
121     private Header applicationHeader;
122     private Group applicationFooter;
123     private Group buildFooter;
124     private String applicationTitleText;
125 
126     // sticky flags
127     private boolean stickyTopGroup;
128     private boolean stickyBreadcrumbs;
129     private boolean stickyHeader;
130     private boolean stickyApplicationHeader;
131     private boolean stickyFooter;
132     private boolean stickyApplicationFooter;
133 
134     private List<String> contentContainerCssClasses;
135 
136     // Breadcrumbs
137     private Breadcrumbs breadcrumbs;
138     private BreadcrumbOptions breadcrumbOptions;
139     private BreadcrumbItem breadcrumbItem;
140     private ParentLocation parentLocation;
141     private List<BreadcrumbItem> pathBasedBreadcrumbs;
142 
143     // Growls support
144     private Growls growls;
145     private boolean growlMessagingEnabled;
146 
147     private BlockUI refreshBlockUI;
148     private BlockUI navigationBlockUI;
149 
150     private String entryPageId;
151 
152     @RequestParameter
153     private String currentPageId;
154 
155     private Group navigation;
156 
157     private Class<?> formClass;
158     private String defaultBindingObjectPath;
159     private Map<String, Class<?>> objectPathToConcreteClassMapping;
160 
161     private List<String> additionalScriptFiles;
162     private List<String> additionalCssFiles;
163     private List<HeadLink> additionalHeadLinks;
164     private List<MetaTag> additionalMetaTags;
165     private boolean useLibraryCssClasses;
166 
167     private ViewType viewTypeName;
168 
169     protected ViewIndex viewIndex;
170     private Map<String, String> viewRequestParameters;
171 
172     private boolean persistFormToSession;
173     private ViewSessionPolicy sessionPolicy;
174 
175     private int preloadPoolSize;
176 
177     private ViewPresentationController presentationController;
178     private ViewAuthorizer authorizer;
179     private Class<? extends RequestAuthorizationCache> requestAuthorizationCacheClass;
180 
181     private Map<String, Boolean> actionFlags;
182     private Map<String, Boolean> editModes;
183 
184     private Map<String, String> expressionVariables;
185 
186     private boolean singlePageView;
187     private boolean mergeWithPageItems;
188     private PageGroup page;
189 
190     @ReferenceCopy(referenceTransient=true)
191     private PageGroup currentPage;
192 
193     private List<Group> dialogs;
194 
195     protected boolean applyDirtyCheck;
196     private boolean translateCodesOnReadOnlyDisplay;
197     private boolean supportsRequestOverrideOfReadOnlyFields;
198     private boolean disableNativeAutocomplete;
199     private boolean disableBrowserCache;
200 
201     private String preLoadScript;
202 
203     @DelayedCopy
204     private List<? extends Component> items;
205 
206     private List<String> viewTemplates;
207 
208     private Class<? extends ViewHelperService> viewHelperServiceClass;
209 
210     @ReferenceCopy
211     private ViewHelperService viewHelperService;
212 
213     private Map<String, Object> preModelContext;
214 
215     public View() {
216         singlePageView = false;
217         mergeWithPageItems = true;
218         translateCodesOnReadOnlyDisplay = false;
219         viewTypeName = ViewType.DEFAULT;
220         formClass = UifFormBase.class;
221         supportsRequestOverrideOfReadOnlyFields = true;
222         disableBrowserCache = true;
223         persistFormToSession = true;
224         sessionPolicy = new ViewSessionPolicy();
225 
226         this.viewIndex = new ViewIndex();
227 
228         additionalScriptFiles = Collections.emptyList();
229         additionalCssFiles = Collections.emptyList();
230         additionalHeadLinks = Collections.emptyList();
231         additionalMetaTags = Collections.emptyList();
232         objectPathToConcreteClassMapping = Collections.emptyMap();
233         viewRequestParameters = Collections.emptyMap();
234         expressionVariables = Collections.emptyMap();
235 
236         requestAuthorizationCacheClass = RequestAuthorizationCache.class;
237 
238         dialogs = Collections.emptyList();
239 
240         items = Collections.emptyList();
241         viewTemplates = new LifecycleAwareList<String>(this);
242     }
243 
244     /**
245      * The following initialization is performed:
246      *
247      * <ul>
248      * <li>If a single paged view, set items in page group and put the page in
249      * the items list</li>
250      * <li>If {@link ViewSessionPolicy#enableTimeoutWarning} is enabled add the session timeout dialogs to the
251      * views list of dialog groups</li>
252      * </ul>
253      *
254      * {@inheritDoc}
255      */
256     @SuppressWarnings("unchecked")
257     @Override
258     public void performInitialization(Object model) {
259         if (model instanceof UifFormBase) {
260             UifFormBase form = (UifFormBase) model;
261 
262             // set view page to page requested on form
263             if (StringUtils.isNotBlank(form.getPageId())) {
264                 setCurrentPageId(form.getPageId());
265             }
266 
267             String dialogId = form.getActionParamaterValue(UifParameters.DIALOG_ID);
268             if (StringUtils.isNotBlank(dialogId)) {
269                 form.setShowDialogId(dialogId);
270 
271                 // initialize the view to open the dialog if necessary
272                 initializeDialogLoadScript((UifFormBase) model);
273             }
274         }
275 
276         initializeDialogReclocationScript();
277 
278         super.performInitialization(model);
279 
280         assert this == ViewLifecycle.getView();
281 
282         // populate items on page for single paged view
283         if (singlePageView) {
284             if (page != null) {
285                 // remove default sections of page when requested
286                 if (!mergeWithPageItems) {
287                     page.setItems(new ArrayList<Group>());
288                 }
289 
290                 // add the items configured on the view to the page items, and set as the
291                 // new page items
292                 List<Component> newItems = (List<Component>) page.getItems();
293                 newItems.addAll(items);
294                 page.setItems(newItems);
295 
296                 page.sortItems();
297 
298                 // reset the items list to include the one page
299                 items = new ArrayList<Group>();
300                 ((List<Group>) items).add(page);
301             }
302         }
303         // if items is only size one and instance of page, set singlePageView to true
304         else if ((this.items != null) && (this.items.size() == 1)) {
305             Component itemComponent = this.items.get(0);
306 
307             if (itemComponent instanceof PageGroup) {
308                 this.singlePageView = true;
309             }
310         }
311 
312         if (sessionPolicy.isEnableTimeoutWarning()) {
313             Group warningDialog = ComponentFactory.getSessionTimeoutWarningDialog();
314             warningDialog.setId(ComponentFactory.SESSION_TIMEOUT_WARNING_DIALOG);
315             getDialogs().add(warningDialog);
316 
317             Group timeoutDialog = ComponentFactory.getSessionTimeoutDialog();
318             timeoutDialog.setId(ComponentFactory.SESSION_TIMEOUT_DIALOG);
319             getDialogs().add(timeoutDialog);
320         }
321 
322         breadcrumbOptions.setupBreadcrumbs(model);
323     }
324 
325     /**
326      * Helper method to set the view's load script to open a dialog.
327      *
328      * @param form the form containing data
329      */
330     protected void initializeDialogLoadScript(UifFormBase form) {
331         // check for edit line dialog action parameters and if present, then show the dialog once page is loaded
332         String selectedCollectionPath = form.getActionParamaterValue(UifParameters.SELECTED_COLLECTION_PATH);
333         String selectedLineIndex = form.getActionParamaterValue(UifParameters.SELECTED_LINE_INDEX);
334         String dialogId = form.getShowDialogId();
335 
336         if (StringUtils.isNotBlank(dialogId) && StringUtils.isNotBlank(selectedCollectionPath) && StringUtils
337                 .isNotBlank(selectedLineIndex)) {
338             // the line index and the collection path need adjusted to account for nested collections within a
339             // dialog because the dialog is shown per the root collection, but the index and path are per
340             // the nested sub-collection
341             String originalLineIndex = StringUtils.substring(dialogId, dialogId.length() - 1);
342             if (!selectedLineIndex.equals(originalLineIndex)) {
343                 selectedLineIndex = originalLineIndex;
344             }
345 
346             // adjust the collection path to point to the root collection rather than the sub-collection
347             if (selectedCollectionPath.contains(UifPropertyPaths.DIALOG_DATA_OBJECT)) {
348                 String collectionId = StringUtils.substring(dialogId, dialogId.indexOf("_") + 1, dialogId.lastIndexOf(
349                         "_"));
350                 BindingInfo bindingInfo = (BindingInfo) form.getViewPostMetadata().getComponentPostMetadata(
351                         collectionId).getData("bindingInfo");
352                 selectedCollectionPath = bindingInfo.getBindingPath();
353             }
354 
355             String actionScript = "setupImages();";
356             if (StringUtils.startsWith(dialogId, ComponentFactory.EDIT_LINE_DIALOG)) {
357                 actionScript +=
358                         "showEditLineDialog('" + dialogId + "', '" + selectedCollectionPath + "', " + selectedLineIndex
359                                 + ");";
360             } else {
361                 String additionalData = "{ 'actionParameters[selectedCollectionPath]' : '" + selectedCollectionPath
362                         + "', 'actionParameters[selectedLineIndex]' : '0' }";
363                 actionScript += "showDialog('" + dialogId + "', " + additionalData + ");";
364             }
365             setOnLoadScript(ScriptUtils.appendScript(getOnLoadScript(), actionScript));
366         } else if (StringUtils.isNotBlank(dialogId) && !(this instanceof LookupView)) {
367             String actionScript =
368                     "jQuery.unblockUI();setupImages();showLoading('Loading...', window.document);showDialog('"
369                             + dialogId + "');";
370             setOnLoadScript(ScriptUtils.appendScript(getOnLoadScript(), actionScript));
371         }
372     }
373 
374     /**
375      * Without this script dialogs might be associated with a non visible action (i.e. inside a dropdown menu).  This
376      * would cause the dialog not being displayed.  To ensure that all dialog are able to be displayed the DIVs of the
377      * dialogs are moved to a common location.
378      */
379     protected void initializeDialogReclocationScript() {
380         String dialogRelocationScript = "jQuery('.modal').appendTo('#Uif-Dialogs');";
381         setOnLoadScript(ScriptUtils.appendScript(getOnLoadScript(), dialogRelocationScript));
382     }
383 
384     /**
385      * {@inheritDoc}
386      */
387     @Override
388     public void afterEvaluateExpression() {
389         super.afterEvaluateExpression();
390 
391         if (getReadOnly() == null) {
392             setReadOnly(false);
393         }
394     }
395 
396     /**
397      * The following updates are done here:
398      *
399      * <ul>
400      * <li>Invoke expression evaluation on view theme</li>
401      * <li>Invoke theme to configure defaults</li>
402      * </ul>
403      */
404     @Override
405     public void performApplyModel(Object model, LifecycleElement parent) {
406         super.performApplyModel(model, parent);
407 
408         View view = ViewLifecycle.getView();
409         if (theme != null) {
410             ViewLifecycle.getExpressionEvaluator().evaluateExpressionsOnConfigurable(view, theme, getContext());
411 
412             theme.configureThemeDefaults();
413         }
414 
415         if (sessionPolicy != null) {
416             ViewLifecycle.getExpressionEvaluator().evaluateExpressionsOnConfigurable(view, sessionPolicy, getContext());
417         }
418 
419         //handle parentLocation breadcrumb chain
420         parentLocation.constructParentLocationBreadcrumbItems(view, model, view.getContext());
421     }
422 
423     /**
424      * The following is performed:
425      *
426      * <ul>
427      * <li>Adds to its document ready script the setupValidator js function for setting
428      * up the validator for this view</li>
429      * </ul>
430      *
431      * {@inheritDoc}
432      */
433     @SuppressWarnings("unchecked")
434     @Override
435     public void performFinalize(Object model, LifecycleElement parent) {
436         super.performFinalize(model, parent);
437 
438         assert this == ViewLifecycle.getView();
439 
440         String preLoadScript = "";
441         if (this.getPreLoadScript() != null) {
442             preLoadScript = this.getPreLoadScript();
443         }
444 
445         // Retrieve Growl and BlockUI settings
446         Growls gw = getGrowls();
447         if (!gw.getTemplateOptions().isEmpty()) {
448             preLoadScript += "setGrowlDefaults(" + gw.getTemplateOptionsJSString() + ");";
449         }
450 
451         BlockUI navBlockUI = getNavigationBlockUI();
452         if (!navBlockUI.getTemplateOptions().isEmpty()) {
453             preLoadScript += "setBlockUIDefaults("
454                     + navBlockUI.getTemplateOptionsJSString()
455                     + ", '"
456                     + UifConstants.BLOCKUI_NAVOPTS
457                     + "');";
458         }
459 
460         BlockUI refBlockUI = getRefreshBlockUI();
461         if (!refBlockUI.getTemplateOptions().isEmpty()) {
462             preLoadScript += "setBlockUIDefaults("
463                     + refBlockUI.getTemplateOptionsJSString()
464                     + ", '"
465                     + UifConstants.BLOCKUI_REFRESHOPTS
466                     + "');";
467         }
468 
469         this.setPreLoadScript(preLoadScript);
470 
471         String onReadyScript = "";
472         if (this.getOnDocumentReadyScript() != null) {
473             onReadyScript = this.getOnDocumentReadyScript();
474         }
475 
476         // initialize session timers for giving timeout warnings
477         if (sessionPolicy.isEnableTimeoutWarning()) {
478             // warning minutes gives us the time before the timeout occurs to give the warning,
479             // so we need to determine how long that should be from the session start
480             int sessionTimeoutInterval = ((UifFormBase) model).getSessionTimeoutInterval();
481             int sessionWarningSeconds = sessionPolicy.getTimeoutWarningSeconds();
482 
483             if (sessionTimeoutInterval <= 0) {
484                 // force to one hour when no setting detected
485                 sessionTimeoutInterval = 3600;
486             }
487 
488             if (sessionWarningSeconds >= sessionTimeoutInterval  || sessionWarningSeconds <= 0) {
489                 sessionWarningSeconds = sessionTimeoutInterval/10;
490             }
491 
492             int sessionWarningInterval = sessionTimeoutInterval - sessionWarningSeconds;
493 
494             onReadyScript = ScriptUtils.appendScript(onReadyScript, ScriptUtils.buildFunctionCall(
495                     UifConstants.JsFunctions.INITIALIZE_SESSION_TIMERS, sessionWarningInterval,
496                     sessionTimeoutInterval));
497         }
498 
499         onReadyScript = ScriptUtils.appendScript(onReadyScript, "jQuery.extend(jQuery.validator.messages, "
500                 + ClientValidationUtils.generateValidatorMessagesOption()
501                 + ");");
502 
503         this.setOnDocumentReadyScript(onReadyScript);
504 
505         // Breadcrumb handling
506         breadcrumbOptions.finalizeBreadcrumbs(model, this, breadcrumbItem);
507 
508         // Add validation default js options for validation framework to View's data attributes
509         Object groupValidationDataDefaults = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
510                 UifConstants.GROUP_VALIDATION_DEFAULTS_MAP_ID);
511         Object fieldValidationDataDefaults = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
512                 UifConstants.FIELD_VALIDATION_DEFAULTS_MAP_ID);
513         Object actionDataDefaults = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
514                 UifConstants.ACTION_DEFAULTS_MAP_ID);
515         Object requiredIndicator = KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
516                 UifConstants.REQUIRED_INDICATOR_ID);
517 
518         // Add data defaults for common components to the view for use in js (to reduce size of individual components)
519         this.addScriptDataAttribute(UifConstants.DataAttributes.GROUP_VALIDATION_DEFAULTS, ScriptUtils.convertToJsValue(
520                 (Map<String, String>) groupValidationDataDefaults));
521         this.addScriptDataAttribute(UifConstants.DataAttributes.FIELD_VALIDATION_DEFAULTS, ScriptUtils.convertToJsValue(
522                 (Map<String, String>) fieldValidationDataDefaults));
523         this.addScriptDataAttribute(UifConstants.DataAttributes.ACTION_DEFAULTS, ScriptUtils.convertToJsValue(
524                 (Map<String, String>) actionDataDefaults));
525         this.addScriptDataAttribute(UifConstants.DataAttributes.REQ_INDICATOR, (String) requiredIndicator);
526 
527         // give view role attribute for js selections
528         this.addDataAttribute(UifConstants.DataAttributes.ROLE, UifConstants.RoleTypes.VIEW);
529 
530         // Add state mapping to post metadata
531         ViewLifecycle.getViewPostMetadata().addComponentPostData(this, "stateObjectBindingPath",
532                 stateObjectBindingPath);
533         ViewLifecycle.getViewPostMetadata().addComponentPostData(this, "stateMapping", stateMapping);
534 
535         setNestedComponentId(getInstructionalMessage(), this.getId() + UifConstants.IdSuffixes.INSTRUCTIONAL);
536 
537         Header header = getHeader();
538         Help help = getHelp();
539 
540         if (header != null) {
541             setNestedComponentId(header, this.getId() + UifConstants.IdSuffixes.HEADER_WRAPPER);
542             setNestedComponentId(header.getUpperGroup(), this.getId() + UifConstants.IdSuffixes.HEADER_UPPER_GROUP);
543             setNestedComponentId(header.getRightGroup(), this.getId() + UifConstants.IdSuffixes.HEADER_RIGHT_GROUP);
544             setNestedComponentId(header.getLowerGroup(), this.getId() + UifConstants.IdSuffixes.HEADER_LOWER_GROUP);
545             setNestedComponentId(help, this.getId() + UifConstants.IdSuffixes.HELP_WRAPPER);
546             if (help != null && help.getHelpAction() != null) {
547                 setNestedComponentId(help.getHelpAction(), this.getId() + UifConstants.IdSuffixes.HELP_WRAPPER);
548             }
549         }
550     }
551 
552     /**
553      * Helper method for setting a new ID for the nested components
554      *
555      * @param component component to adjust ID for
556      * @param newId
557      */
558     protected void setNestedComponentId(Component component, String newId) {
559         if (component != null) {
560             component.setId(newId);
561         }
562     }
563 
564     /**
565      * {@inheritDoc}
566      */
567     @Override
568     public void notifyCompleted(ViewLifecyclePhase phase) {
569         super.notifyCompleted(phase);
570 
571         if (phase.getViewPhase().equals(UifConstants.ViewPhases.FINALIZE)) {
572             ViewLifecycle.getHelper().performCustomViewFinalize(ViewLifecycle.getModel());
573         }
574     }
575 
576     /**
577      * Gets all breadcrumb items related to this view's parent location.
578      *
579      * @return breadcrumb items
580      */
581     public List<BreadcrumbItem> getBreadcrumbItems() {
582         if (parentLocation == null) {
583             return Collections.emptyList();
584         }
585 
586         List<BreadcrumbItem> breadcrumbItems = new ArrayList<BreadcrumbItem>();
587         breadcrumbItems.add(parentLocation.getPageBreadcrumbItem());
588         breadcrumbItems.add(parentLocation.getViewBreadcrumbItem());
589         for (BreadcrumbItem item : parentLocation.getResolvedBreadcrumbItems()) {
590             if (!breadcrumbItems.contains(item)) {
591                 breadcrumbItems.add(item);
592             }
593         }
594 
595         return breadcrumbItems;
596     }
597 
598     /**
599      * {@inheritDoc}
600      */
601     @Override
602     public Set<Class<? extends Component>> getSupportedComponents() {
603         Set<Class<? extends Component>> supportedComponents = new HashSet<Class<? extends Component>>();
604         supportedComponents.add(Group.class);
605 
606         return supportedComponents;
607     }
608 
609     /**
610      * {@inheritDoc}
611      */
612     @Override
613     public String getComponentTypeName() {
614         return "view";
615     }
616 
617     /**
618      * Iterates through the contained page items and returns the Page that matches the set current page id or
619      * the first page in the case of a single page view.
620      *
621      * @return page group instance
622      */
623     @ViewLifecycleRestriction(exclude = UifConstants.ViewPhases.PRE_PROCESS)
624     public PageGroup getCurrentPage() {
625         if (currentPage != null) {
626             return currentPage;
627         }
628 
629         for (Component item : this.getItems()) {
630             if (!(item instanceof PageGroup)) {
631                 continue;
632             }
633 
634             if (singlePageView || StringUtils.equals(item.getId(), getCurrentPageId())) {
635                 currentPage = (PageGroup) CopyUtils.unwrap(item);
636             }
637         }
638 
639         return currentPage;
640     }
641 
642     /**
643      * Getter for returning the view's items and page for inclusion in the pre-process phase.
644      *
645      * <p>Note this is necessary so we get IDs assigned for all the pages during the pre-process phase. For other
646      * phases, only the current page is picked up.</p>
647      *
648      * @return list of components to include for the pre-process phase
649      */
650     @ViewLifecycleRestriction(UifConstants.ViewPhases.PRE_PROCESS)
651     public List<Component> getPagesForPreprocessing() {
652         List<Component> processProcessItems = new ArrayList<Component>();
653 
654         if (getItems() != null) {
655             processProcessItems.addAll(getItems());
656         }
657 
658         if (getPage() != null) {
659             processProcessItems.add(getPage());
660         }
661 
662         return processProcessItems;
663     }
664 
665     /**
666      * {@inheritDoc}
667      */
668     @Override
669     public void sortItems() {
670         if (!singlePageView) {
671             super.sortItems();
672         }
673     }
674 
675     /**
676      * Namespace code the view should be associated with.
677      *
678      * <p>The namespace code is used within the framework in such places as permission checks and parameter
679      * retrieval</p>
680      *
681      * @return namespace code
682      */
683     @BeanTagAttribute
684     public String getNamespaceCode() {
685         return namespaceCode;
686     }
687 
688     /**
689      * @see View#getNamespaceCode()
690      */
691     public void setNamespaceCode(String namespaceCode) {
692         checkMutable(true);
693         this.namespaceCode = namespaceCode;
694     }
695 
696     /**
697      * View name provides an identifier for a view within a type. That is if a
698      * set of View instances have the same values for the
699      * properties that are used to retrieve them by their type, the name can be
700      * given to further qualify the view that should be retrieved.
701      *
702      * <p>A view type like the LookupView might have several views for
703      * the same object class, but one that is the 'default' lookup and another
704      * that is the 'advanced' lookup. Therefore the name on the first could be
705      * set to 'default', and likewise the name for the second 'advanced'</p>
706      *
707      * @return name of view
708      */
709     @BeanTagAttribute
710     public String getViewName() {
711         return this.viewName;
712     }
713 
714     /**
715      * @see View#getViewName()
716      */
717     public void setViewName(String viewName) {
718         checkMutable(true);
719         this.viewName = viewName;
720     }
721 
722     /**
723      * When true, this view will use a unified header - the page header will be omitted and its title will be used
724      * in the ViewHeader supportTitle property (dynamically updated on page change).
725      *
726      * @return true if using a unified header
727      */
728     @BeanTagAttribute
729     public boolean isUnifiedHeader() {
730         return unifiedHeader;
731     }
732 
733     /**
734      * @see View#isUnifiedHeader()
735      */
736     public void setUnifiedHeader(boolean unifiedHeader) {
737         checkMutable(true);
738         this.unifiedHeader = unifiedHeader;
739     }
740 
741     /**
742      * TopGroup is an optional group of content that appears above the breadcrumbs and view header.
743      *
744      * @return the topGroup component
745      */
746     @BeanTagAttribute
747     public Group getTopGroup() {
748         return topGroup;
749     }
750 
751     /**
752      * @see View#getTopGroup()
753      */
754     public void setTopGroup(Group topGroup) {
755         checkMutable(true);
756         this.topGroup = topGroup;
757     }
758 
759     /**
760      * Header for the application containing the view.
761      *
762      * <p>When deploying outside a portal, the application header and footer property can be configured to
763      * display a consistent header/footer across all views. Here application logos, menus, login controls
764      * and so on can be rendered</p>
765      *
766      * @return application header
767      */
768     @BeanTagAttribute
769     public Header getApplicationHeader() {
770         return applicationHeader;
771     }
772 
773     /**
774      * @see View#getApplicationHeader()
775      */
776     public void setApplicationHeader(Header applicationHeader) {
777         checkMutable(true);
778         this.applicationHeader = applicationHeader;
779     }
780 
781     /**
782      * Footer for the application containing the view.
783      *
784      * <p>When deploying outside a portal, the application header and footer property can be configured to
785      * display a consistent header/footer across all views. Here such things as application links, copyrights
786      * and so on can be rendered</p>
787      *
788      * @return application footer
789      */
790     @BeanTagAttribute
791     public Group getApplicationFooter() {
792         return applicationFooter;
793     }
794 
795     /**
796      * @see View#getApplicationFooter()
797      */
798     public void setApplicationFooter(Group applicationFooter) {
799         checkMutable(true);
800         this.applicationFooter = applicationFooter;
801     }
802 
803     /**
804      * Build footer for the application containing the view.
805      *
806      * @return build footer
807      */
808     @BeanTagAttribute
809     public Group getBuildFooter() {
810         return buildFooter;
811     }
812 
813     /**
814      * @see View#getBuildFooter()
815      */
816     public void setBuildFooter(Group buildFooter) {
817         this.buildFooter = buildFooter;
818     }
819 
820     /**
821      * Title text to be displayed on browser tab.
822      *
823      * @return
824      */
825     @BeanTagAttribute(name = "applicationTitleText")
826     public String getApplicationTitleText() {
827         return applicationTitleText;
828     }
829 
830     /**
831      * @see View#getApplicationTitleText()
832      */
833     public void setApplicationTitleText(String applicationTitleText) {
834         this.applicationTitleText = applicationTitleText;
835     }
836 
837     /**
838      * If true, the top group will be sticky (fixed to top of window).
839      *
840      * @return true if the top group is sticky, false otherwise
841      */
842     @BeanTagAttribute
843     public boolean isStickyTopGroup() {
844         return stickyTopGroup;
845     }
846 
847     /**
848      * @see View#isStickyTopGroup()
849      */
850     public void setStickyTopGroup(boolean stickyTopGroup) {
851         checkMutable(true);
852         this.stickyTopGroup = stickyTopGroup;
853     }
854 
855     /**
856      * If true, the breadcrumb widget will be sticky (fixed to top of window).
857      *
858      * @return true if breadcrumbs are sticky, false otherwise
859      */
860     @BeanTagAttribute
861     public boolean isStickyBreadcrumbs() {
862         return stickyBreadcrumbs;
863     }
864 
865     /**
866      * @see View#isStickyBreadcrumbs()
867      */
868     public void setStickyBreadcrumbs(boolean stickyBreadcrumbs) {
869         checkMutable(true);
870         this.stickyBreadcrumbs = stickyBreadcrumbs;
871     }
872 
873     /**
874      * If true, the ViewHeader for this view will be sticky (fixed to top of window).
875      *
876      * @return true if the header is sticky, false otherwise
877      */
878     @BeanTagAttribute
879     public boolean isStickyHeader() {
880         if (this.getHeader() != null && this.getHeader() instanceof ViewHeader) {
881             return ((ViewHeader) this.getHeader()).isSticky();
882         } else {
883             return false;
884         }
885     }
886 
887     /**
888      * @see View#isStickyHeader()
889      */
890     public void setStickyHeader(boolean stickyHeader) {
891         checkMutable(true);
892         this.stickyHeader = stickyHeader;
893         if (this.getHeader() != null && this.getHeader() instanceof ViewHeader) {
894             ((ViewHeader) this.getHeader()).setSticky(stickyHeader);
895         }
896     }
897 
898     /**
899      * Set to true to make the applicationHeader sticky (fixed to top of window)
900      *
901      * @return true if applicationHeader is sticky, false otherwise
902      */
903     @BeanTagAttribute
904     public boolean isStickyApplicationHeader() {
905         return stickyApplicationHeader;
906     }
907 
908     /**
909      * @see View#isStickyApplicationHeader()
910      */
911     public void setStickyApplicationHeader(boolean stickyApplicationHeader) {
912         checkMutable(true);
913         this.stickyApplicationHeader = stickyApplicationHeader;
914     }
915 
916     /**
917      * If true, the view footer will become sticky (fixed to bottom of window).
918      *
919      * @return ture if the view footer is sticky, false otherwise
920      */
921     @BeanTagAttribute
922     public boolean isStickyFooter() {
923         return stickyFooter;
924     }
925 
926     /**
927      * @see View#isStickyFooter()
928      */
929     public void setStickyFooter(boolean stickyFooter) {
930         checkMutable(true);
931         this.stickyFooter = stickyFooter;
932         if (this.getFooter() != null) {
933             this.getFooter().addDataAttribute(UifConstants.DataAttributes.STICKY_FOOTER, Boolean.toString(
934                     stickyFooter));
935         }
936     }
937 
938     /**
939      * If true, the applicationFooter will become sticky (fixed to bottom of window).
940      *
941      * @return true if the application footer is sticky, false otherwise
942      */
943     @BeanTagAttribute
944     public boolean isStickyApplicationFooter() {
945         return stickyApplicationFooter;
946     }
947 
948     /**
949      * @see View#isStickyApplicationFooter()
950      */
951     public void setStickyApplicationFooter(boolean stickyApplicationFooter) {
952         checkMutable(true);
953         this.stickyApplicationFooter = stickyApplicationFooter;
954     }
955 
956     /**
957      * List of CSS style classes that will be applied to a div that wraps the content.
958      *
959      * <p>Wrapping the content gives the ability to move between a fluid width container or a fixed width
960      * container. The div is also wraps content inside sticky elements (header and footer), so visual treatment
961      * can be given to the full width of the screen while restricting the area of the content.</p>
962      *
963      * <p>In Bootstrap, use 'container-fluid' for a fluid width container, and 'container' for a fixed width
964      * container.</p>
965      *
966      * @return List of css classes to apply to content wrapper div
967      */
968     @BeanTagAttribute
969     public List<String> getContentContainerCssClasses() {
970         return contentContainerCssClasses;
971     }
972 
973     /**
974      * @see View#getContentContainerCssClasses()
975      */
976     public void setContentContainerCssClasses(List<String> contentContainerCssClasses) {
977         this.contentContainerCssClasses = contentContainerCssClasses;
978     }
979 
980     /**
981      * Returns the list of {@link View#getContentContainerCssClasses()} as a concatenated string (each class
982      * is separated by a space).
983      *
984      * @return String of content css classes
985      */
986     public String getContentContainerClassesAsString() {
987         if (contentContainerCssClasses != null) {
988             return StringUtils.join(contentContainerCssClasses, " ");
989         }
990 
991         return "";
992     }
993 
994     /**
995      * Specifies what page should be rendered by default. This is the page that
996      * will be rendered when the View is first rendered or when the
997      * current page is not set
998      *
999      * @return id of the page to render by default
1000      */
1001     @BeanTagAttribute
1002     public String getEntryPageId() {
1003         return this.entryPageId;
1004     }
1005 
1006     /**
1007      * @see View#getEntryPageId()
1008      */
1009     public void setEntryPageId(String entryPageId) {
1010         checkMutable(true);
1011         this.entryPageId = entryPageId;
1012     }
1013 
1014     /**
1015      * The id for the page within the view that should be displayed in the UI. Other pages of the view will not be
1016      * rendered.
1017      *
1018      * <p>If current page id is not set, it is set to the configured entry page or first item in list id</p>
1019      *
1020      * @return id of the page that should be displayed
1021      */
1022     public String getCurrentPageId() {
1023         // default current page if not set
1024         if (StringUtils.isBlank(currentPageId)) {
1025             if (StringUtils.isNotBlank(entryPageId)) {
1026                 currentPageId = entryPageId;
1027             } else if ((getItems() != null) && !getItems().isEmpty()) {
1028                 Component firstPageGroup = getItems().get(0);
1029                 if (firstPageGroup instanceof PageGroup) {
1030                     currentPageId = firstPageGroup.getId();
1031                 }
1032             }
1033         }
1034 
1035         return this.currentPageId;
1036     }
1037 
1038     /**
1039      * @see View#getCurrentPageId()
1040      */
1041     public void setCurrentPageId(String currentPageId) {
1042         checkMutable(true);
1043         this.currentPageId = currentPageId;
1044         this.currentPage = null;
1045     }
1046 
1047     /**
1048      * NavigationGroup instance for the View<
1049      *
1050      * <p>Provides configuration necessary to render the navigation. This includes
1051      * navigation items in addition to configuration for the navigation
1052      * renderer</p>
1053      *
1054      * @return NavigationGroup
1055      */
1056     @BeanTagAttribute
1057     public Group getNavigation() {
1058         return this.navigation;
1059     }
1060 
1061     /**
1062      * @see View#getNavigation()
1063      */
1064     public void setNavigation(Group navigation) {
1065         checkMutable(true);
1066         this.navigation = navigation;
1067     }
1068 
1069     /**
1070      * Class of the Form that should be used with the View instance.
1071      *
1072      * <p>The form is the top level object for all the view's data and is
1073      * used to present and accept data in the user interface. All form classes
1074      * should extend UifFormBase</p>
1075      *
1076      * @return class for the view's form
1077      * @see org.kuali.rice.krad.web.form.UifFormBase
1078      */
1079     @BeanTagAttribute
1080     public Class<?> getFormClass() {
1081         return this.formClass;
1082     }
1083 
1084     /**
1085      * @see View#getFormClass()
1086      */
1087     public void setFormClass(Class<?> formClass) {
1088         checkMutable(true);
1089         this.formClass = formClass;
1090     }
1091 
1092     /**
1093      * For View types that work primarily with one nested object of
1094      * the form (for instance document, or bo) the default binding object path
1095      * can be set for each of the views DataBinding components. If
1096      * the component does not set its own binding object path it will inherit
1097      * the default.
1098      *
1099      * @return binding path to the object from the form
1100      */
1101     @BeanTagAttribute
1102     public String getDefaultBindingObjectPath() {
1103         return this.defaultBindingObjectPath;
1104     }
1105 
1106     /**
1107      * @see View#getDefaultBindingObjectPath()
1108      */
1109     public void setDefaultBindingObjectPath(String defaultBindingObjectPath) {
1110         checkMutable(true);
1111         this.defaultBindingObjectPath = defaultBindingObjectPath;
1112     }
1113 
1114     /**
1115      * Configures the concrete classes that will be used for properties in the
1116      * form object graph that have an abstract or interface type.
1117      *
1118      * <p>For properties that have an abstract or interface type, it is not
1119      * possible to perform operations like getting/settings property values and
1120      * getting information from the dictionary. When these properties are
1121      * encountered in the object graph, this Map will be consulted
1122      * to determine the concrete type to use</p>
1123      *
1124      * <p>e.g. Suppose we have a property document.accountingLine.accountNumber and
1125      * the accountingLine property on the document instance has an interface
1126      * type 'AccountingLine'. We can then put an entry into this map with key
1127      * 'document.accountingLine', and value
1128      * 'org.kuali.rice.sampleapp.TravelAccountingLine'. When getting the
1129      * property type or an entry from the dictionary for accountNumber, the
1130      * TravelAccountingLine class will be used</p>
1131      *
1132      * @return Map<String, Class> of class implementations keyed by path
1133      */
1134     @BeanTagAttribute
1135     public Map<String, Class<?>> getObjectPathToConcreteClassMapping() {
1136         if (objectPathToConcreteClassMapping == Collections.EMPTY_MAP && isMutable(true)) {
1137             objectPathToConcreteClassMapping = new HashMap<String, Class<?>>();
1138         }
1139 
1140         return this.objectPathToConcreteClassMapping;
1141     }
1142 
1143     /**
1144      * @see View#getObjectPathToConcreteClassMapping()
1145      */
1146     public void setObjectPathToConcreteClassMapping(Map<String, Class<?>> objectPathToConcreteClassMapping) {
1147         checkMutable(true);
1148         this.objectPathToConcreteClassMapping = objectPathToConcreteClassMapping;
1149     }
1150 
1151     /**
1152      * Declares additional script files that should be included with the
1153      * View.
1154      *
1155      * <p>These files are brought into the HTML page along with
1156      * common script files configured for the Rice application. Each entry
1157      * contain the path to the CSS file, either a relative path, path from web
1158      * root, or full URI</p>
1159      *
1160      * <p>e.g. '/krad/scripts/myScript.js', '../scripts/myScript.js',
1161      * 'http://my.edu/web/myScript.js'</p>;
1162      *
1163      * @return script file locations
1164      */
1165     @BeanTagAttribute
1166     public List<String> getAdditionalScriptFiles() {
1167         if (additionalScriptFiles == Collections.EMPTY_LIST && isMutable(true)) {
1168             additionalScriptFiles = new LifecycleAwareList<String>(this);
1169         }
1170 
1171         return additionalScriptFiles;
1172     }
1173 
1174     /**
1175      * @see View#getAdditionalScriptFiles()
1176      */
1177     public void setAdditionalScriptFiles(List<String> additionalScriptFiles) {
1178         checkMutable(true);
1179         if (additionalScriptFiles == null) {
1180             this.additionalScriptFiles = Collections.emptyList();
1181         } else {
1182             this.additionalScriptFiles = new LifecycleAwareList<String>(this, additionalScriptFiles);
1183         }
1184     }
1185 
1186     /**
1187      * Declares additional CSS files that should be included with the View.
1188      *
1189      * <p>These files are brought into the HTML page along with
1190      * common CSS files configured for the Rice application. Each entry should
1191      * contain the path to the CSS file, either a relative path, path from web
1192      * root, or full URI</p>
1193      *
1194      * <p>e.g. '/krad/css/stacked-view.css', '../css/stacked-view.css',
1195      * 'http://my.edu/web/stacked-view.css'</p>;
1196      *
1197      * @return CSS file locations
1198      */
1199     @BeanTagAttribute
1200     public List<String> getAdditionalCssFiles() {
1201         if (additionalCssFiles == Collections.EMPTY_LIST && isMutable(true)) {
1202             additionalCssFiles = new LifecycleAwareList<String>(this);
1203         }
1204 
1205         return additionalCssFiles;
1206     }
1207 
1208     /**
1209      * @see View#getAdditionalCssFiles()
1210      */
1211     public void setAdditionalCssFiles(List<String> additionalCssFiles) {
1212         checkMutable(true);
1213         if (additionalCssFiles == null) {
1214             this.additionalCssFiles = Collections.emptyList();
1215         } else {
1216             this.additionalCssFiles = new LifecycleAwareList<String>(this, additionalCssFiles);
1217         }
1218     }
1219 
1220     /**
1221      * List of additional link tags that should be included with the View in the html head.
1222      *
1223      * @return headlink objects
1224      */
1225     @BeanTagAttribute
1226     public List<HeadLink> getAdditionalHeadLinks() {
1227         return additionalHeadLinks;
1228     }
1229 
1230     /**
1231      * @see View#getAdditionalHeadLinks()
1232      */
1233     public void setAdditionalHeadLinks(List<HeadLink> additionalHeadLinks) {
1234         this.additionalHeadLinks = additionalHeadLinks;
1235     }
1236 
1237     /**
1238      * List of additional meta tags that should be included with the View in the html head tag.
1239      *
1240      * @return   additionalMetaTags
1241      */
1242     @BeanTagAttribute
1243     public List<MetaTag> getAdditionalMetaTags() {
1244         return additionalMetaTags;
1245     }
1246 
1247     /**
1248      * @see View#getAdditionalMetaTags()
1249      */
1250     public void setAdditionalMetaTags(List<MetaTag> additionalMetaTags) {
1251         this.additionalMetaTags = additionalMetaTags;
1252     }
1253 
1254     /**
1255      * True if the libraryCssClasses set on components will be output to their class attribute, false otherwise.
1256      *
1257      * @return true if using libraryCssClasses on components
1258      */
1259     @BeanTagAttribute
1260     public boolean isUseLibraryCssClasses() {
1261         return useLibraryCssClasses;
1262     }
1263 
1264     /**
1265      * @see View#isUseLibraryCssClasses()
1266      */
1267     public void setUseLibraryCssClasses(boolean useLibraryCssClasses) {
1268         checkMutable(true);
1269         this.useLibraryCssClasses = useLibraryCssClasses;
1270     }
1271 
1272     /**
1273      * List of templates that are used to render the view.
1274      *
1275      * <p>This list will be populated by unique template names as the components of the view are being processed.
1276      * Additional templates can be added in the view configuration if desired. At the beginning of the the view
1277      * rendering, each template in the list will then be included or processed by the template language</p>
1278      *
1279      * <p>Note the user of this depends on the template language being used for rendering. Some languages might require
1280      * including the template for each component instance (for example JSP templates). While others might simply
1281      * include markup that is then available for rendering each component instance (for example FreeMarker which has
1282      * a macro for each component that the template defines)</p>
1283      *
1284      * @return list of template names that should be included for rendering the view
1285      */
1286     public List<String> getViewTemplates() {
1287         return viewTemplates;
1288     }
1289 
1290     /**
1291      * Adds a template to the views include list.
1292      *
1293      * @param template path to template to add
1294      */
1295     public void addViewTemplate(String template) {
1296         if (StringUtils.isEmpty(template)) {
1297             return;
1298         }
1299 
1300         if (!viewTemplates.contains(template)) {
1301             synchronized (viewTemplates) {
1302                 viewTemplates.add(template);
1303             }
1304         }
1305     }
1306 
1307     /**
1308      * Setter for the the list of template names that should be included to render the view
1309      *
1310      * @param viewTemplates
1311      */
1312     public void setViewTemplates(List<String> viewTemplates) {
1313         checkMutable(true);
1314 
1315         if (viewTemplates == null) {
1316             this.viewTemplates = new LifecycleAwareList<String>(this);
1317         } else {
1318             this.viewTemplates = new LifecycleAwareList<String>(this, viewTemplates);
1319         }
1320     }
1321 
1322     /**
1323      * View type name the view is associated with the view instance
1324      *
1325      * <p>Views that share common features and functionality can be grouped by the
1326      * view type. Usually view types extend the <code>View</code> class to
1327      * provide additional configuration and to set defaults. View types can also
1328      * implement the <code>ViewTypeService</code> to add special indexing and
1329      * retrieval of views</p>
1330      *
1331      * @return view type name for the view
1332      */
1333     @BeanTagAttribute
1334     public ViewType getViewTypeName() {
1335         return this.viewTypeName;
1336     }
1337 
1338     /**
1339      * @see View#getViewTypeName()
1340      */
1341     public void setViewTypeName(ViewType viewTypeName) {
1342         checkMutable(true);
1343         this.viewTypeName = viewTypeName;
1344     }
1345 
1346     /**
1347      * Class name of the ViewHelperService that handles the various phases of the Views lifecycle.
1348      *
1349      * @return Class for the spring bean
1350      * @see org.kuali.rice.krad.uif.service.ViewHelperService
1351      */
1352     @BeanTagAttribute
1353     public Class<? extends ViewHelperService> getViewHelperServiceClass() {
1354         return this.viewHelperServiceClass;
1355     }
1356 
1357     /**
1358      * Setter for the <code>ViewHelperService</code> class name
1359      * Also initializes the viewHelperService
1360      *
1361      * @param viewHelperServiceClass
1362      */
1363     public void setViewHelperServiceClass(Class<? extends ViewHelperService> viewHelperServiceClass) {
1364         checkMutable(true);
1365         this.viewHelperServiceClass = viewHelperServiceClass;
1366         if ((this.viewHelperService == null) && (this.viewHelperServiceClass != null)) {
1367             viewHelperService = KRADUtils.createNewObjectFromClass(viewHelperServiceClass);
1368         }
1369     }
1370 
1371     /**
1372      * Creates the <code>ViewHelperService</code> associated with the View
1373      *
1374      * @return ViewHelperService instance
1375      */
1376     @BeanTagAttribute
1377     public ViewHelperService getViewHelperService() {
1378         return viewHelperService;
1379     }
1380 
1381     /**
1382      * @see View#getViewHelperServiceClass()
1383      */
1384     public void setViewHelperService(ViewHelperService viewHelperService) {
1385         checkMutable(true);
1386         this.viewHelperService = viewHelperService;
1387     }
1388 
1389     /**
1390      * Invoked to produce a ViewIndex of the current view's components
1391      */
1392     public void clearIndex() {
1393         if (this.viewIndex == null) {
1394             this.viewIndex = new ViewIndex();
1395         }
1396         this.viewIndex.clearIndex(this);
1397     }
1398 
1399     /**
1400      * Holds field indexes of the View instance for retrieval.
1401      *
1402      * @return ViewIndex instance
1403      */
1404     public ViewIndex getViewIndex() {
1405         return this.viewIndex;
1406     }
1407 
1408     /**
1409      * Map of parameters from the request that set view options, used to rebuild
1410      * the view on each post
1411      *
1412      * <p>
1413      * Views can be configured by parameters. These might impact which parts of
1414      * the view are rendered or how the view behaves. Generally these would get
1415      * passed in when a new view is requested (by request parameters). These
1416      * will be used to initially populate the view properties. In addition, on a
1417      * post the view will be rebuilt and properties reset again by the allow
1418      * request parameters.
1419      * </p>
1420      *
1421      * <p>
1422      * Example parameter would be for MaintenaceView whether a New, Edit, or
1423      * Copy was requested (maintenance mode)
1424      * </p>
1425      *
1426      * @return
1427      */
1428     public Map<String, String> getViewRequestParameters() {
1429         return this.viewRequestParameters;
1430     }
1431 
1432     /**
1433      * @see View#getViewRequestParameters()
1434      */
1435     public void setViewRequestParameters(Map<String, String> viewRequestParameters) {
1436         checkMutable(true);
1437         this.viewRequestParameters = Collections.unmodifiableMap(viewRequestParameters);
1438     }
1439 
1440     /**
1441      * Indicates whether the form (model) associated with the view should be stored in the user session.
1442      *
1443      * <p>The form class (or model) is used to hold the data that backs the view along with the built view object. Storing
1444      * the form instance in session allows many things:
1445      *
1446      * <ul>
1447      * <li>Data does not need to be rebuilt for each server request (for example a collection)</li>
1448      * <li>Data that does not need to go to the user can remain on the form, reducing the size of the response and
1449      * improving security</li>
1450      * <li>Data can be keep around in a 'pre-save' state. When requested by the user changes can then be persisted to
1451      * the database</li>
1452      * <li>Certain information about the view that was rendered, such as input fields, collection paths, and refresh
1453      * components can be kept on the form to support UI interaction</li>
1454      * </ul>
1455      *
1456      * Setting this flag to false will prevent the form from being kept in session and as a result will limit what can
1457      * be done by the framework. In almost all cases this is not recommended</p>
1458      *
1459      * <p>Note all forms will be cleared when the user session expires (based on the rice configuration). In addition, the
1460      * framework enables clear points on certain actions to remove the form when it is no longer needed</p>
1461      *
1462      * @return true if the form should be stored in the user session, false if only request based
1463      */
1464     @BeanTagAttribute
1465     public boolean isPersistFormToSession() {
1466         return persistFormToSession;
1467     }
1468 
1469     /**
1470      * @see View#isPersistFormToSession()
1471      */
1472     public void setPersistFormToSession(boolean persistFormToSession) {
1473         checkMutable(true);
1474         this.persistFormToSession = persistFormToSession;
1475     }
1476 
1477     /**
1478      * Configures behavior that should occur when a session timeout occurs on the view.
1479      *
1480      * @return view session policy instance
1481      */
1482     @BeanTagAttribute
1483     public ViewSessionPolicy getSessionPolicy() {
1484         return sessionPolicy;
1485     }
1486 
1487     /**
1488      * @see View#getSessionPolicy()
1489      */
1490     public void setSessionPolicy(ViewSessionPolicy sessionPolicy) {
1491         checkMutable(true);
1492         this.sessionPolicy = sessionPolicy;
1493     }
1494 
1495     public int getPreloadPoolSize() {
1496         return preloadPoolSize;
1497     }
1498 
1499     public void setPreloadPoolSize(int preloadPoolSize) {
1500         this.preloadPoolSize = preloadPoolSize;
1501     }
1502 
1503     /**
1504      * PresentationController that should be used for the View instance.
1505      *
1506      * <p>The presentation controller is consulted to determine component (group,
1507      * field) state such as required, read-only, and hidden. The presentation
1508      * controller does not take into account user permissions. The presentation
1509      * controller can also output action flags and edit modes that will be set
1510      * onto the view instance and can be referred to by conditional expressions</p>
1511      *
1512      * @return PresentationController
1513      */
1514     @BeanTagAttribute
1515     public ViewPresentationController getPresentationController() {
1516         return this.presentationController;
1517     }
1518 
1519     /**
1520      * @see View#getPresentationController()
1521      */
1522     public void setPresentationController(ViewPresentationController presentationController) {
1523         checkMutable(true);
1524         this.presentationController = presentationController;
1525     }
1526 
1527     /**
1528      * Setter for the view's presentation controller by class
1529      *
1530      * @param presentationControllerClass
1531      */
1532     public void setPresentationControllerClass(
1533             Class<? extends ViewPresentationController> presentationControllerClass) {
1534         checkMutable(true);
1535         this.presentationController = KRADUtils.createNewObjectFromClass(presentationControllerClass);
1536     }
1537 
1538     /**
1539      * Authorizer that should be used for the View instance
1540      *
1541      * <p>The authorizer class is consulted to determine component (group, field)
1542      * state such as required, read-only, and hidden based on the users
1543      * permissions. It typically communicates with the Kuali Identity Management
1544      * system to determine roles and permissions. It is used with the
1545      * presentation controller and dictionary conditional logic to determine the
1546      * final component state. The authorizer can also output action flags and
1547      * edit modes that will be set onto the view instance and can be referred to
1548      * by conditional expressions</p>
1549      *
1550      * @return Authorizer
1551      */
1552     @BeanTagAttribute
1553     public ViewAuthorizer getAuthorizer() {
1554         return this.authorizer;
1555     }
1556 
1557     /**
1558      * @see View#getAuthorizer()
1559      */
1560     public void setAuthorizer(ViewAuthorizer authorizer) {
1561         checkMutable(true);
1562         this.authorizer = authorizer;
1563     }
1564 
1565     /**
1566      * Setter for the view's authorizer by class
1567      *
1568      * @param authorizerClass
1569      */
1570     public void setAuthorizerClass(Class<? extends ViewAuthorizer> authorizerClass) {
1571         checkMutable(true);
1572         this.authorizer = KRADUtils.createNewObjectFromClass(authorizerClass);
1573     }
1574 
1575     /**
1576      * Map of strings that flag what actions can be taken in the UI.
1577      *
1578      * <p>These can be used in conditional expressions in the dictionary or by
1579      * other UI logic</p>
1580      *
1581      * @return action flags
1582      */
1583     @BeanTagAttribute
1584     public Map<String, Boolean> getActionFlags() {
1585         return this.actionFlags;
1586     }
1587 
1588     /**
1589      * @see View#getActionFlags()
1590      */
1591     public void setActionFlags(Map<String, Boolean> actionFlags) {
1592         this.actionFlags = actionFlags;
1593     }
1594 
1595     /**
1596      * Map of edit modes that enabled for the view.
1597      *
1598      * <p>These can be used in conditional expressions in the dictionary or by
1599      * other UI logic</p>
1600      *
1601      * @return edit modes
1602      */
1603     @BeanTagAttribute
1604     public Map<String, Boolean> getEditModes() {
1605         return this.editModes;
1606     }
1607 
1608     /**
1609      * @see View#getEditModes()
1610      */
1611     public void setEditModes(Map<String, Boolean> editModes) {
1612         this.editModes = editModes;
1613     }
1614 
1615     public Class<? extends RequestAuthorizationCache> getRequestAuthorizationCacheClass() {
1616         return requestAuthorizationCacheClass;
1617     }
1618 
1619     public void setRequestAuthorizationCacheClass(
1620             Class<? extends RequestAuthorizationCache> requestAuthorizationCacheClass) {
1621         this.requestAuthorizationCacheClass = requestAuthorizationCacheClass;
1622     }
1623 
1624     /**
1625      * Map that contains expressions to evaluate and make available as variables
1626      * for conditional expressions within the view.
1627      *
1628      * <p>Each Map entry contains one expression variables, where the map key gives
1629      * the name for the variable, and the map value gives the variable
1630      * expression. The variables expressions will be evaluated before
1631      * conditional logic is run and made available as variables for other
1632      * conditional expressions. Variable expressions can be based on the model
1633      * and any object contained in the view's context</p>
1634      *
1635      * @return variable expressions
1636      */
1637     @BeanTagAttribute
1638     public Map<String, String> getExpressionVariables() {
1639         return this.expressionVariables;
1640     }
1641 
1642     /**
1643      * @see View#getExpressionVariables()
1644      */
1645     public void setExpressionVariables(Map<String, String> expressionVariables) {
1646         checkMutable(true);
1647         this.expressionVariables = Collections.unmodifiableMap(expressionVariables);
1648     }
1649 
1650     /**
1651      * Indicates whether the View only has a single page
1652      * Group or contains multiple page Group instances.
1653      *
1654      * <p>In the case of a single page it is assumed the group's items
1655      * list contains the section groups for the page, and the page itself is
1656      * given by the page property ({@link #getPage()}. This is for convenience
1657      * of configuration and also can drive other configuration like styling</p>
1658      *
1659      * @return true if the view only contains one page group, false if
1660      * it contains multple pages
1661      */
1662     @BeanTagAttribute
1663     public boolean isSinglePageView() {
1664         return this.singlePageView;
1665     }
1666 
1667     /**
1668      * @see View#isSinglePageView()
1669      */
1670     public void setSinglePageView(boolean singlePageView) {
1671         checkMutable(true);
1672         this.singlePageView = singlePageView;
1673     }
1674 
1675     /**
1676      * Indicates whether the default sections specified in the page items list
1677      * should be included for this view.  This only applies to single paged views.
1678      *
1679      * @return true if the view should contain the default sections
1680      * specified in the page
1681      */
1682     @BeanTagAttribute
1683     public boolean isMergeWithPageItems() {
1684         return mergeWithPageItems;
1685     }
1686 
1687     /**
1688      * @see View#isMergeWithPageItems()
1689      */
1690     public void setMergeWithPageItems(boolean mergeWithPageItems) {
1691         checkMutable(true);
1692         this.mergeWithPageItems = mergeWithPageItems;
1693     }
1694 
1695     /**
1696      * For single paged views ({@link #isSinglePageView()}, gives the page
1697      * Group the view should render. The actual items for the page
1698      * is taken from the group's items list ({@link #getItems()}, and set onto
1699      * the give page group. This is for convenience of configuration.
1700      *
1701      * @return page group for single page views
1702      */
1703     @ViewLifecycleRestriction
1704     @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECT)
1705     public PageGroup getPage() {
1706         return this.page;
1707     }
1708 
1709     /**
1710      * @see View#getPage()
1711      */
1712     public void setPage(PageGroup page) {
1713         checkMutable(true);
1714         this.page = page;
1715     }
1716 
1717     /**
1718      * {@inheritDoc}
1719      */
1720     @Override
1721     @ViewLifecycleRestriction
1722     @BeanTagAttribute
1723     public List<? extends Component> getItems() {
1724         if (items == Collections.EMPTY_LIST && isMutable(true)) {
1725             items = new LifecycleAwareList<Component>(this);
1726         }
1727 
1728         return items;
1729     }
1730 
1731     /**
1732      * {@inheritDoc}
1733      */
1734     @SuppressWarnings("unchecked")
1735     @Override
1736     public void setItems(List<? extends Component> items) {
1737         checkMutable(true);
1738 
1739         if (items == null) {
1740             this.items = Collections.emptyList();
1741         } else {
1742             // TODO: Fix this unchecked condition.
1743             this.items = new LifecycleAwareList<Component>(this, (List<Component>) items);
1744         }
1745     }
1746 
1747     /**
1748      * Provide a list of dialog groups associated with this view.
1749      *
1750      * @return List of dialog Groups
1751      */
1752     @BeanTagAttribute
1753     @ViewLifecycleRestriction(value = UifConstants.ViewPhases.FINALIZE,
1754             condition = "!ajaxRequest || (ajaxReturnType eq 'update-view') || (ajaxReturnType eq 'update-page')")
1755     public List<Group> getDialogs() {
1756         if (dialogs == Collections.EMPTY_LIST && isMutable(true)) {
1757             dialogs = new LifecycleAwareList<Group>(this);
1758         }
1759 
1760         return dialogs;
1761     }
1762 
1763     /**
1764      * @see View#getDialogs()
1765      */
1766     public void setDialogs(List<Group> dialogs) {
1767         checkMutable(true);
1768 
1769         if (dialogs == null) {
1770             this.dialogs = Collections.emptyList();
1771         } else {
1772             this.dialogs = new LifecycleAwareList<Group>(this, dialogs);
1773         }
1774     }
1775 
1776     /**
1777      * Breadcrumb widget used for displaying homeward path and history
1778      *
1779      * @return the breadcrumbs
1780      */
1781     @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1782     public Breadcrumbs getBreadcrumbs() {
1783         return this.breadcrumbs;
1784     }
1785 
1786     /**
1787      * @param breadcrumbs the breadcrumbs to set
1788      */
1789     public void setBreadcrumbs(Breadcrumbs breadcrumbs) {
1790         checkMutable(true);
1791         this.breadcrumbs = breadcrumbs;
1792     }
1793 
1794     /**
1795      * The breadcrumbOptions for this view.
1796      *
1797      * <p>Render options set at the view level are always ignored (only apply to
1798      * page level BreadcrumbOptions).  BreadcrumbOptions for homewardPathBreadcrumbs,
1799      * preViewBreadcrumbs, prePageBreadcrumbs,
1800      * and breadcrumbOverrides are inherited by
1801      * child pages unless they override them themselves.</p>
1802      *
1803      * @return the BreadcrumbOptions for this view
1804      */
1805     @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1806     public BreadcrumbOptions getBreadcrumbOptions() {
1807         return breadcrumbOptions;
1808     }
1809 
1810     /**
1811      * @see View#getBreadcrumbOptions()
1812      */
1813     public void setBreadcrumbOptions(BreadcrumbOptions breadcrumbOptions) {
1814         checkMutable(true);
1815         this.breadcrumbOptions = breadcrumbOptions;
1816     }
1817 
1818     /**
1819      * The View's breadcrumbItem defines settings for the breadcrumb which appears in the breadcrumb list for this
1820      * view.
1821      *
1822      * @return the breadcrumbItem
1823      */
1824     @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1825     public BreadcrumbItem getBreadcrumbItem() {
1826         return breadcrumbItem;
1827     }
1828 
1829     /**
1830      * @see View#getBreadcrumbItem()
1831      */
1832     public void setBreadcrumbItem(BreadcrumbItem breadcrumbItem) {
1833         checkMutable(true);
1834         this.breadcrumbItem = breadcrumbItem;
1835     }
1836 
1837     /**
1838      * The parentLocation defines urls that represent the parent of a View in a conceptial site hierarchy.
1839      *
1840      * <p>By defining a parent with these urls defined, a breadcrumb chain can be generated and displayed automatically
1841      * before this View's breadcrumbItem(s).  To chain multiple views, the urls must be defining viewId and
1842      * controllerMapping settings instead of setting an href directly (this will end the chain).  If labels are
1843      * not set on parentLocations, the labels will attempt to be derived from parent views/pages breadcrumbItem
1844      * and headerText - if these contain expressions which cannot be evaluated in the current context an exception
1845      * will be thrown</p>
1846      *
1847      * @return the parentLocation
1848      */
1849     @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1850     public ParentLocation getParentLocation() {
1851         return parentLocation;
1852     }
1853 
1854     /**
1855      * @see View#getParentLocation()
1856      */
1857     public void setParentLocation(ParentLocation parentLocation) {
1858         checkMutable(true);
1859         this.parentLocation = parentLocation;
1860     }
1861 
1862     /**
1863      * The pathBasedBreadcrumbs for this View.  These can only be set by the framework.
1864      *
1865      * @return the path based breadcrumbs
1866      */
1867     public List<BreadcrumbItem> getPathBasedBreadcrumbs() {
1868         return pathBasedBreadcrumbs;
1869     }
1870 
1871     /**
1872      * The pathBasedBreadcrumbs for this View.
1873      *
1874      * @param pathBasedBreadcrumbs
1875      */
1876     public void setPathBasedBreadcrumbs(List<BreadcrumbItem> pathBasedBreadcrumbs) {
1877         checkMutable(true);
1878         this.pathBasedBreadcrumbs = pathBasedBreadcrumbs == null ? null : new LifecycleAwareList<BreadcrumbItem>(this,
1879                 pathBasedBreadcrumbs);
1880     }
1881 
1882     /**
1883      * Growls widget which sets up global settings for the growls used in this
1884      * view and its pages.
1885      *
1886      * @return the growls
1887      */
1888     @BeanTagAttribute
1889     public Growls getGrowls() {
1890         return this.growls;
1891     }
1892 
1893     /**
1894      * @param growls the growls to set
1895      */
1896     public void setGrowls(Growls growls) {
1897         checkMutable(true);
1898         this.growls = growls;
1899     }
1900 
1901     /**
1902      * @return returns the refresh block object
1903      */
1904     @BeanTagAttribute
1905     public BlockUI getRefreshBlockUI() {
1906         return refreshBlockUI;
1907     }
1908 
1909     /**
1910      * Set the refresh BlockUI used with single element blocking
1911      * (such as ajax based element loading/updates)
1912      *
1913      * @param refreshBlockUI
1914      */
1915     public void setRefreshBlockUI(BlockUI refreshBlockUI) {
1916         checkMutable(true);
1917         this.refreshBlockUI = refreshBlockUI;
1918     }
1919 
1920     /**
1921      * @return returns the navigation block object
1922      */
1923     @BeanTagAttribute
1924     public BlockUI getNavigationBlockUI() {
1925         return navigationBlockUI;
1926     }
1927 
1928     /**
1929      * Set the navigation BlockUI used with single page blocking
1930      * (such as full page loading/saving)
1931      *
1932      * @param navigationBlockUI
1933      */
1934     public void setNavigationBlockUI(BlockUI navigationBlockUI) {
1935         checkMutable(true);
1936         this.navigationBlockUI = navigationBlockUI;
1937     }
1938 
1939     /**
1940      * Whether to use growls to show messages - info, warning and error
1941      *
1942      * <p>Growls use the messages contained in the message map. If enabled, info
1943      * messages in their entirety will be displayed in growls, for warning and
1944      * error messages a growl message will notify the user that these messages
1945      * exist on the page.</p>
1946      *
1947      * <p> If this setting is disabled, it is recommended that
1948      * infoMessage display be enabled for the page ValidationMessages bean in order to
1949      * display relevant information to the user. Note: the growl scripts are
1950      * built out in the PageGroup class.</p>
1951      *
1952      * @return the growlMessagingEnabled
1953      */
1954     @BeanTagAttribute
1955     public boolean isGrowlMessagingEnabled() {
1956         return this.growlMessagingEnabled;
1957     }
1958 
1959     /**
1960      * enable or disable showing of messages using growls
1961      *
1962      * @param growlMessagingEnabled the growlMessagingEnabled to set
1963      */
1964     public void setGrowlMessagingEnabled(boolean growlMessagingEnabled) {
1965         checkMutable(true);
1966         this.growlMessagingEnabled = growlMessagingEnabled;
1967     }
1968 
1969     /**
1970      * Indicates whether the form should be validated for dirtyness.
1971      *
1972      * <p>For FormView, it's necessary to validate when the user tries to navigate out of the form. If set, all the
1973      * InputFields will be validated on refresh, navigate, cancel or close Action or on form
1974      * unload and if dirty, displays a message and user can decide whether to continue with
1975      * the action or stay on the form. For lookup and inquiry, it's not needed to validate</p>
1976      *
1977      * @return true if dirty validation is set
1978      */
1979     @BeanTagAttribute
1980     public boolean isApplyDirtyCheck() {
1981         return this.applyDirtyCheck;
1982     }
1983 
1984     /**
1985      * @see View#isApplyDirtyCheck()
1986      */
1987     public void setApplyDirtyCheck(boolean applyDirtyCheck) {
1988         checkMutable(true);
1989         this.applyDirtyCheck = applyDirtyCheck;
1990     }
1991 
1992     /**
1993      * Returns whether the current view supports displaying <code>KualiCode</code>'s name as additional display value
1994      *
1995      * @return true if the current view supports
1996      */
1997     @BeanTagAttribute
1998     public boolean isTranslateCodesOnReadOnlyDisplay() {
1999         return translateCodesOnReadOnlyDisplay;
2000     }
2001 
2002     /**
2003      * Indicates whether the Name of the Code should be displayed when a property is of type <code>KualiCode</code>
2004      *
2005      * @param translateCodesOnReadOnlyDisplay indicates whether KualiCode's name should be included.
2006      */
2007     public void setTranslateCodesOnReadOnlyDisplay(boolean translateCodesOnReadOnlyDisplay) {
2008         checkMutable(true);
2009         this.translateCodesOnReadOnlyDisplay = translateCodesOnReadOnlyDisplay;
2010     }
2011 
2012     /**
2013      * Indicates whether the view allows read only fields to be specified on the request URL which will
2014      * override the view setting.
2015      *
2016      * <p>If enabled, the readOnlyFields request parameter can be sent to indicate fields that should be set read only</p>
2017      *
2018      * @return true if read only request overrides are allowed, false if not
2019      */
2020     @BeanTagAttribute
2021     public boolean isSupportsRequestOverrideOfReadOnlyFields() {
2022         return supportsRequestOverrideOfReadOnlyFields;
2023     }
2024 
2025     /**
2026      * @see View#isSupportsRequestOverrideOfReadOnlyFields()
2027      */
2028     public void setSupportsRequestOverrideOfReadOnlyFields(boolean supportsRequestOverrideOfReadOnlyFields) {
2029         checkMutable(true);
2030         this.supportsRequestOverrideOfReadOnlyFields = supportsRequestOverrideOfReadOnlyFields;
2031     }
2032 
2033     /**
2034      * Indicates whether the browser autocomplete functionality should be disabled for the
2035      * entire form (adds autocomplete="off").
2036      *
2037      * <p>The browser's native autocomplete functionality can cause issues with security fields and also fields
2038      * with the UIF suggest widget enabled</p>
2039      *
2040      * @return true if the native autocomplete should be turned off for the form, false if not
2041      */
2042     @BeanTagAttribute
2043     public boolean isDisableNativeAutocomplete() {
2044         return disableNativeAutocomplete;
2045     }
2046 
2047     /**
2048      * @see View#isDisableNativeAutocomplete()
2049      */
2050     public void setDisableNativeAutocomplete(boolean disableNativeAutocomplete) {
2051         checkMutable(true);
2052         this.disableNativeAutocomplete = disableNativeAutocomplete;
2053     }
2054 
2055     /**
2056      * Enables functionality to bust the browsers cache by appending an unique cache key.
2057      *
2058      * <p>Since response headers are unreliable for preventing caching in all browsers, the
2059      * framework uses a technique for updating the URL to include an unique cache key. If the
2060      * HTML 5 History API is supported a parameter can be added to the URL which causes the browser
2061      * to not find the cached page when the user goes back. If not the framework falls back to using
2062      * a hash key and resubmitting using script to pull the latest</p>
2063      *
2064      * @return true if cache for the view should be disabled, false if not
2065      */
2066     @BeanTagAttribute
2067     public boolean isDisableBrowserCache() {
2068         return disableBrowserCache;
2069     }
2070 
2071     /**
2072      * @see View#isDisableBrowserCache()
2073      */
2074     public void setDisableBrowserCache(boolean disableBrowserCache) {
2075         checkMutable(true);
2076         this.disableBrowserCache = disableBrowserCache;
2077     }
2078 
2079     /**
2080      * Script that is executed at the beginning of page load (before any other script).
2081      *
2082      * <p>Many used to set server variables client side</p>
2083      *
2084      * @return pre load script
2085      */
2086     @BeanTagAttribute
2087     public String getPreLoadScript() {
2088         return preLoadScript;
2089     }
2090 
2091     /**
2092      * @see View#getPreLoadScript()
2093      */
2094     public void setPreLoadScript(String preLoadScript) {
2095         checkMutable(true);
2096         this.preLoadScript = preLoadScript;
2097     }
2098 
2099     /**
2100      * The theme which contains stylesheets for this view.
2101      *
2102      * @return ViewTheme
2103      */
2104     @BeanTagAttribute
2105     public ViewTheme getTheme() {
2106         return theme;
2107     }
2108 
2109     /**
2110      * @see View#getTheme()
2111      */
2112     public void setTheme(ViewTheme theme) {
2113         checkMutable(true);
2114         this.theme = theme;
2115     }
2116 
2117     /**
2118      * The stateObject's binding path, this will be used along with the StateMapping's statePropertyName to
2119      * determine what field in the model state information is stored in for this view.  Used during View validation.
2120      *
2121      * @return stateObjectBindingPath path to the object storing state information
2122      */
2123     @BeanTagAttribute
2124     public String getStateObjectBindingPath() {
2125         return stateObjectBindingPath;
2126     }
2127 
2128     /**
2129      * The stateObject's binding path, this will be used along with the StateMapping's statePropertyName to
2130      * determine what field in the model state information is stored in for this view.  Used during View validation.
2131      *
2132      * @param stateObjectBindingPath
2133      */
2134     public void setStateObjectBindingPath(String stateObjectBindingPath) {
2135         checkMutable(true);
2136         this.stateObjectBindingPath = stateObjectBindingPath;
2137     }
2138 
2139     /**
2140      * Gets the stateMapping.
2141      *
2142      * <p>The state mapping object is used to determine the state information for a view,
2143      * it must include an ordered list of states, and where to find the state information for the view.
2144      * A stateMapping must be set for state based validation to occur.  When stateMapping information is
2145      * not included, the view's model is considered stateless and all constraints will apply regardless of their
2146      * state information or replacements (ie, they will function as they did in version 2.1).</p>
2147      *
2148      * @return information needed for state based validation, if null no state based validation
2149      * functionality will exist and configured constraints will apply regardless of state
2150      * @since 2.2
2151      */
2152     @BeanTagAttribute
2153     public StateMapping getStateMapping() {
2154         return stateMapping;
2155     }
2156 
2157     /**
2158      * @see View#getStateMapping()
2159      */
2160     public void setStateMapping(StateMapping stateMapping) {
2161         checkMutable(true);
2162         this.stateMapping = stateMapping;
2163     }
2164 
2165     /**
2166      * Returns the general context that is available before the apply model phase (during the
2167      * initialize phase)
2168      *
2169      * @return context map
2170      */
2171     public Map<String, Object> getPreModelContext() {
2172         if (preModelContext == null) {
2173             Map<String, Object> context = new HashMap<String, Object>();
2174 
2175             context.put(UifConstants.ContextVariableNames.VIEW, this);
2176             context.put(UifConstants.ContextVariableNames.VIEW_HELPER, viewHelperService);
2177 
2178             ViewTheme theme = getTheme();
2179             if (theme != null) {
2180                 context.put(UifConstants.ContextVariableNames.THEME_IMAGES, theme.getImageDirectory());
2181             }
2182 
2183             Map<String, String> properties = CoreApiServiceLocator.getKualiConfigurationService().getAllProperties();
2184             context.put(UifConstants.ContextVariableNames.CONFIG_PROPERTIES, properties);
2185             context.put(UifConstants.ContextVariableNames.CONSTANTS, KRADConstants.class);
2186             context.put(UifConstants.ContextVariableNames.UIF_CONSTANTS, UifConstants.class);
2187             context.put(UifConstants.ContextVariableNames.USER_SESSION, GlobalVariables.getUserSession());
2188 
2189             preModelContext = Collections.unmodifiableMap(context);
2190         }
2191 
2192         return preModelContext;
2193     }
2194 
2195     /**
2196      * @see org.kuali.rice.krad.uif.component.ComponentBase#clone()
2197      */
2198     @Override
2199     public View clone() throws CloneNotSupportedException {
2200         View viewCopy = (View) super.clone();
2201         viewCopy.viewIndex = new ViewIndex();
2202         return viewCopy;
2203     }
2204 
2205     /**
2206      * {@inheritDoc}
2207      */
2208     @Override
2209     public void completeValidation(ValidationTrace tracer) {
2210         tracer.addBean(this);
2211 
2212         // Check for the presence of a valid item with an not-null EntryPageId
2213         boolean validPageId = false;
2214         if (getEntryPageId() != null) {
2215             for (int i = 0; i < getItems().size(); i++) {
2216                 if (getEntryPageId().compareTo(getItems().get(i).getId()) == 0) {
2217                     validPageId = true;
2218                 }
2219             }
2220         } else {
2221             validPageId = true;
2222         }
2223         if (!validPageId) {
2224             String currentValues[] = {"entryPageId = " + getEntryPageId()};
2225             tracer.createError("Items must contain an item with a matching id to entryPageId", currentValues);
2226         }
2227 
2228         // Check to insure the view as not already been set
2229         if (tracer.getValidationStage() == ValidationTrace.START_UP) {
2230             if (getViewStatus().compareTo(ViewStatus.CREATED) != 0) {
2231                 String currentValues[] = {"viewStatus = " + getViewStatus()};
2232                 tracer.createError("ViewStatus should not be set", currentValues);
2233             }
2234         }
2235 
2236         // Check to insure the binding object path is a valid property
2237         boolean validDefaultBindingObjectPath = false;
2238         if (getDefaultBindingObjectPath() == null) {
2239             validDefaultBindingObjectPath = true;
2240         } else if (DataDictionary.isPropertyOf(getFormClass(), getDefaultBindingObjectPath())) {
2241             validDefaultBindingObjectPath = true;
2242         }
2243         if (!validDefaultBindingObjectPath) {
2244             String currentValues[] =
2245                     {"formClass = " + getFormClass(), "defaultBindingPath = " + getDefaultBindingObjectPath()};
2246             tracer.createError("DefaultBingdingObjectPath must be a valid property of the formClass", currentValues);
2247         }
2248 
2249         // Check to insure the page is set if the view is a single page
2250         if (isSinglePageView()) {
2251             if (getPage() == null) {
2252                 String currentValues[] = {"singlePageView = " + isSinglePageView(), "page = " + getPage()};
2253                 tracer.createError("Page must be set if singlePageView is true", currentValues);
2254             }
2255             for (int i = 0; i < getItems().size(); i++) {
2256                 if (getItems().get(i).getClass() == PageGroup.class) {
2257                     String currentValues[] =
2258                             {"singlePageView = " + isSinglePageView(), "items(" + i + ") = " + getItems().get(i)
2259                                     .getClass()};
2260                     tracer.createError("Items cannot be pageGroups if singlePageView is true", currentValues);
2261                 }
2262             }
2263         }
2264 
2265         // Checks to insure the Growls are set if growl messaging is enabled
2266         if (isGrowlMessagingEnabled() == true && getGrowls() == null) {
2267             if (Validator.checkExpressions(this, "growls")) {
2268                 String currentValues[] =
2269                         {"growlMessagingEnabled = " + isGrowlMessagingEnabled(), "growls = " + getGrowls()};
2270                 tracer.createError("Growls cannot be null if Growl Messaging is enabled", currentValues);
2271             }
2272         }
2273 
2274         // Checks that there are items present if the view is not a single page
2275         if (!isSinglePageView()) {
2276             if (getItems().size() == 0) {
2277                 String currentValues[] =
2278                         {"singlePageView = " + isSinglePageView(), "items.size = " + getItems().size()};
2279                 tracer.createWarning("Items cannot be empty if singlePageView is false", currentValues);
2280             } else {
2281                 for (int i = 0; i < getItems().size(); i++) {
2282                     if (getItems().get(i).getClass() != PageGroup.class) {
2283                         String currentValues[] =
2284                                 {"singlePageView = " + isSinglePageView(), "items(" + i + ") = " + getItems().get(i)
2285                                         .getClass()};
2286                         tracer.createError("Items must be pageGroups if singlePageView is false", currentValues);
2287                     }
2288                 }
2289             }
2290         }
2291         super.completeValidation(tracer.getCopy());
2292     }
2293 
2294 }