View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.element;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.exception.RiceRuntimeException;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
23  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
24  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.UifParameters;
27  import org.kuali.rice.krad.uif.UifPropertyPaths;
28  import org.kuali.rice.krad.uif.component.Component;
29  import org.kuali.rice.krad.uif.component.ComponentSecurity;
30  import org.kuali.rice.krad.uif.field.DataField;
31  import org.kuali.rice.krad.uif.field.DataFieldSecurity;
32  import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
33  import org.kuali.rice.krad.uif.util.ExpressionUtils;
34  import org.kuali.rice.krad.uif.util.ScriptUtils;
35  import org.kuali.rice.krad.uif.view.FormView;
36  import org.kuali.rice.krad.uif.view.View;
37  
38  import java.util.ArrayList;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  
43  /**
44   * Field that presents an action that can be taken on the UI such as submitting
45   * the form or invoking a script
46   *
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  @BeanTags({@BeanTag(name = "action-bean", parent = "Uif-Action"), @BeanTag(name = "actionImage-bean", parent = "Uif-ActionImage"),
50          @BeanTag(name = "primaryActionButton-bean", parent = "Uif-PrimaryActionButton"),
51          @BeanTag(name = "secondaryActionButton-bean", parent = "Uif-SecondaryActionButton"),
52          @BeanTag(name = "primaryActionButton-small-bean", parent = "Uif-PrimaryActionButton-Small"),
53          @BeanTag(name = "secondaryActionButton-small-bean", parent = "Uif-SecondaryActionButton-Small"),
54          @BeanTag(name = "actionLink-bean", parent = "Uif-ActionLink"),
55          @BeanTag(name = "navigationActionLink-bean", parent = "Uif-NavigationActionLink"),
56          @BeanTag(name = "navigationActionButton-bean", parent = "Uif-NavigationActionButton"),
57          @BeanTag(name = "secondaryNavigationActionButton-bean", parent = "Uif-SecondaryNavigationActionButton"),
58          @BeanTag(name = "helpAction-bean", parent = "Uif-HelpAction"),
59          @BeanTag(name = "saveAction-bean", parent = "Uif-SaveAction"),
60          @BeanTag(name = "closeAction-bean", parent = "Uif-CloseAction"),
61          @BeanTag(name = "cancelAction-bean", parent = "Uif-CancelAction"),
62          @BeanTag(name = "checkFormAction-bean", parent = "Uif-CheckFormAction"),
63          @BeanTag(name = "addLineAction-bean", parent = "Uif-AddLineAction"),
64          @BeanTag(name = "deleteLineAction-bean", parent = "Uif-DeleteLineAction"),
65          @BeanTag(name = "saveLineAction-bean", parent = "Uif-SaveLineAction"),
66          @BeanTag(name = "addBlankLineAction-bean", parent = "Uif-AddBlankLineAction"),
67          @BeanTag(name = "addViaLightBoxAction-bean", parent = "Uif-AddViaLightBoxAction"),
68          @BeanTag(name = "toggleRowDetailsAction-bean", parent = "Uif-ToggleRowDetailsAction"),
69          @BeanTag(name = "expandDetailsAction-bean", parent = "Uif-ExpandDetailsAction"),
70          @BeanTag(name = "expandDetailsImageAction-bean", parent = "Uif-ExpandDetailsImageAction"),
71          @BeanTag(name = "jumpToTopLink-bean", parent = "Uif-JumpToTopLink"),
72          @BeanTag(name = "jumpToBottomLink-bean", parent = "Uif-JumpToBottomLink"),
73          @BeanTag(name = "expandDisclosuresButton-bean", parent = "Uif-ExpandDisclosuresButton"),
74          @BeanTag(name = "collapseDisclosuresButton-bean", parent = "Uif-CollapseDisclosuresButton"),
75          @BeanTag(name = "showInactiveCollectionItemsButton-bean", parent = "Uif-ShowInactiveCollectionItemsButton"),
76          @BeanTag(name = "hideInactiveCollectionItemsButton-bean", parent = "Uif-HideInactiveCollectionItemsButton"),
77          @BeanTag(name = "collectionQuickFinderAction-bean", parent = "Uif-CollectionQuickFinderAction")})
78  public class Action extends ContentElementBase {
79      private static final long serialVersionUID = 1025672792657238829L;
80  
81      private String methodToCall;
82      private String actionEvent;
83      private String navigateToPageId;
84  
85      private String actionScript;
86  
87      private String actionLabel;
88      private Image actionImage;
89      private String actionImagePlacement;
90  
91      private String jumpToIdAfterSubmit;
92      private String jumpToNameAfterSubmit;
93      private String focusOnIdAfterSubmit;
94  
95      private boolean performClientSideValidation;
96      private boolean performDirtyValidation;
97  
98      private String preSubmitCall;
99      private boolean ajaxSubmit;
100 
101     private String ajaxReturnType;
102     private String refreshId;
103     private String refreshPropertyName;
104 
105     private String successCallback;
106     private String errorCallback;
107 
108     private String loadingMessageText;
109     private boolean disableBlocking;
110 
111     private Map<String, String> additionalSubmitData;
112     private Map<String, String> actionParameters;
113 
114     private boolean evaluateDisabledOnKeyUp;
115 
116     private boolean disabled;
117     private String disabledReason;
118     private String disabledExpression;
119     private String disabledConditionJs;
120     private List<String> disabledConditionControlNames;
121 
122     private List<String> disabledWhenChangedPropertyNames;
123     private List<String> enabledWhenChangedPropertyNames;
124 
125     public Action() {
126         super();
127 
128         actionImagePlacement = UifConstants.Position.LEFT.name();
129 
130         ajaxSubmit = true;
131 
132         successCallback = "";
133         errorCallback = "";
134         preSubmitCall = "";
135 
136         additionalSubmitData = new HashMap<String, String>();
137         actionParameters = new HashMap<String, String>();
138 
139         disabled = false;
140         disabledWhenChangedPropertyNames = new ArrayList<String>();
141         enabledWhenChangedPropertyNames = new ArrayList<String>();
142     }
143 
144     /**
145      * Sets the disabledExpression, if any, evaluates it and sets the disabled property
146      *
147      * @param view - view instance to which the component belongs
148      * @param model - Top level object containing the data (could be the form or a
149      * @param parent
150      */
151     public void performApplyModel(View view, Object model, Component parent) {
152         super.performApplyModel(view, model, parent);
153 
154         disabledExpression = this.getPropertyExpression("disabled");
155         if (disabledExpression != null) {
156             ExpressionEvaluatorService expressionEvaluatorService =
157                     KRADServiceLocatorWeb.getExpressionEvaluatorService();
158 
159             disabledExpression = expressionEvaluatorService.replaceBindingPrefixes(view, this, disabledExpression);
160             disabled = (Boolean) expressionEvaluatorService.evaluateExpression(model, this.getContext(),
161                     disabledExpression);
162         }
163     }
164 
165     /**
166      * The following finalization is performed:
167      *
168      * <ul>
169      * <li>Add methodToCall action parameter if set and setup event code for
170      * setting action parameters</li>
171      * <li>Invoke method to build the data attributes and submit data for the action</li>
172      * <li>Compose the final onclick script for the action</li>
173      * <li>Parses the disabled expressions, if any, to equivalent javascript and evaluates the disable/enable when
174      * changed property names</li>
175      * </ul>
176      *
177      * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
178      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
179      */
180     @Override
181     public void performFinalize(View view, Object model, Component parent) {
182         super.performFinalize(view, model, parent);
183 
184         if (StringUtils.isNotEmpty(disabledExpression)
185                 && !disabledExpression.equalsIgnoreCase("true")
186                 && !disabledExpression.equalsIgnoreCase("false")) {
187             disabledConditionControlNames = new ArrayList<String>();
188             disabledConditionJs = ExpressionUtils.parseExpression(disabledExpression, disabledConditionControlNames);
189         }
190 
191         List<String> adjustedDisablePropertyNames = new ArrayList<String>();
192         for (String propertyName : disabledWhenChangedPropertyNames) {
193             adjustedDisablePropertyNames.add(
194                     KRADServiceLocatorWeb.getExpressionEvaluatorService().replaceBindingPrefixes(view, this,
195                             propertyName));
196         }
197         disabledWhenChangedPropertyNames = adjustedDisablePropertyNames;
198 
199         List<String> adjustedEnablePropertyNames = new ArrayList<String>();
200         for (String propertyName : enabledWhenChangedPropertyNames) {
201             adjustedEnablePropertyNames.add(
202                     KRADServiceLocatorWeb.getExpressionEvaluatorService().replaceBindingPrefixes(view, this,
203                             propertyName));
204         }
205         enabledWhenChangedPropertyNames = adjustedEnablePropertyNames;
206 
207         // clear alt text to avoid screen reader confusion when using image in button with text
208         if (actionImage != null && StringUtils.isNotBlank(actionImagePlacement) && StringUtils.isNotBlank(
209                 actionLabel)) {
210             actionImage.setAltText("");
211         }
212 
213         if (!actionParameters.containsKey(UifConstants.UrlParams.ACTION_EVENT) && StringUtils.isNotBlank(actionEvent)) {
214             actionParameters.put(UifConstants.UrlParams.ACTION_EVENT, actionEvent);
215         }
216 
217         if (StringUtils.isNotBlank(navigateToPageId)) {
218             actionParameters.put(UifParameters.NAVIGATE_TO_PAGE_ID, navigateToPageId);
219             if (StringUtils.isBlank(methodToCall)) {
220                 actionParameters.put(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME,
221                         UifConstants.MethodToCallNames.NAVIGATE);
222             }
223         }
224 
225         if (!actionParameters.containsKey(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME) && StringUtils
226                 .isNotBlank(methodToCall)) {
227             actionParameters.put(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME, methodToCall);
228         }
229 
230         setupRefreshAction(view);
231 
232         buildActionData(view, model, parent);
233 
234         // build final onclick script
235         String onClickScript = this.getOnClickScript();
236         if (StringUtils.isNotBlank(actionScript)) {
237             onClickScript = ScriptUtils.appendScript(onClickScript, actionScript);
238         } else {
239             onClickScript = ScriptUtils.appendScript(onClickScript, "actionInvokeHandler(this);");
240         }
241 
242         // add dirty check if it is enabled for the view and the action requires it
243         if (view instanceof FormView) {
244             if (((FormView) view).isApplyDirtyCheck() && performDirtyValidation) {
245                 onClickScript = "if (checkDirty(e) == false) { " + onClickScript + " ; } ";
246             }
247         }
248 
249         //stop action if the action is disabled
250         if (disabled) {
251             this.addStyleClass("disabled");
252             this.setSkipInTabOrder(true);
253         }
254         onClickScript = "if(jQuery(this).hasClass('disabled')){ return false; }" + onClickScript;
255 
256         setOnClickScript("e.preventDefault();" + onClickScript);
257     }
258 
259     /**
260      * When the action is updating a component sets up the refresh script for the component (found by the
261      * given refresh id or refresh property name)
262      *
263      * @param view - view instance the action belongs to
264      */
265     protected void setupRefreshAction(View view) {
266         // if refresh property or id is given, make return type update component
267         // TODO: what if the refresh id is the page id? we should set the return type as update page
268         if (StringUtils.isNotBlank(refreshPropertyName) || StringUtils.isNotBlank(refreshId)) {
269             ajaxReturnType = UifConstants.AjaxReturnTypes.UPDATECOMPONENT.getKey();
270         }
271 
272         // if refresh property name is given, adjust the binding and then attempt to find the
273         // component in the view index
274         Component refreshComponent = null;
275         if (StringUtils.isNotBlank(refreshPropertyName)) {
276             // TODO: does this support all binding prefixes?
277             if (refreshPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
278                 refreshPropertyName = StringUtils.removeStart(refreshPropertyName, UifConstants.NO_BIND_ADJUST_PREFIX);
279             } else if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
280                 refreshPropertyName = view.getDefaultBindingObjectPath() + "." + refreshPropertyName;
281             }
282 
283             DataField dataField = view.getViewIndex().getDataFieldByPath(refreshPropertyName);
284             if (dataField != null) {
285                 refreshComponent = dataField;
286                 refreshId = refreshComponent.getId();
287             }
288         } else if (StringUtils.isNotBlank(refreshId)) {
289             Component component = view.getViewIndex().getComponentById(refreshId);
290             if (component != null) {
291                 refreshComponent = component;
292             }
293         }
294 
295         if (refreshComponent != null) {
296             refreshComponent.setRefreshedByAction(true);
297 
298             // update initial state
299             Component initialComponent = view.getViewIndex().getInitialComponentStates().get(
300                     refreshComponent.getBaseId());
301             if (initialComponent != null) {
302                 initialComponent.setRefreshedByAction(true);
303                 view.getViewIndex().getInitialComponentStates().put(refreshComponent.getBaseId(), initialComponent);
304             }
305         }
306     }
307 
308     /**
309      * Builds the data attributes that will be read client side to determine how to
310      * handle the action and the additional data that should be submitted with the action
311      *
312      * <p>
313      * Note these data attributes will be exposed as a data map client side. The simple attributes (non object
314      * value) are also written out as attributes on the action element.
315      * </p>
316      *
317      * @param view - view instance the action belongs to
318      * @param model - model object containing the view data
319      * @param parent - component the holds the action
320      */
321     protected void buildActionData(View view, Object model, Component parent) {
322         // map properties to data attributes
323         addDataAttribute("ajaxsubmit", Boolean.toString(ajaxSubmit));
324         addDataAttributeIfNonEmpty("successcallback", this.successCallback);
325         addDataAttributeIfNonEmpty("errorcallback", this.errorCallback);
326         addDataAttributeIfNonEmpty("presubmitcall", this.preSubmitCall);
327         addDataAttributeIfNonEmpty("loadingmessage", this.loadingMessageText);
328         addDataAttributeIfNonEmpty("disableblocking", Boolean.toString(this.disableBlocking));
329         addDataAttributeIfNonEmpty("ajaxreturntype", this.ajaxReturnType);
330         addDataAttributeIfNonEmpty("refreshid", this.refreshId);
331         addDataAttribute("validate", Boolean.toString(this.performClientSideValidation));
332 
333         // all action parameters should be submitted with action
334         Map<String, String> submitData = new HashMap<String, String>();
335         for (String key : actionParameters.keySet()) {
336             String parameterPath = key;
337             if (!key.equals(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME)) {
338                 parameterPath = UifPropertyPaths.ACTION_PARAMETERS + "[" + key + "]";
339             }
340             submitData.put(parameterPath, actionParameters.get(key));
341         }
342 
343         for (String key : additionalSubmitData.keySet()) {
344             submitData.put(key, additionalSubmitData.get(key));
345         }
346 
347         // TODO possibly fix some other way - this is a workaround, prevents
348         // showing history and showing home again on actions which submit the form
349         submitData.put(UifConstants.UrlParams.SHOW_HISTORY, "false");
350         submitData.put(UifConstants.UrlParams.SHOW_HOME, "false");
351 
352         // if focus id not set default to focus on action
353         if (focusOnIdAfterSubmit.equalsIgnoreCase(UifConstants.Order.SELF.toString())) {
354             focusOnIdAfterSubmit = this.getId();
355             submitData.put("focusId", focusOnIdAfterSubmit);
356         } else if (focusOnIdAfterSubmit.equalsIgnoreCase(UifConstants.Order.NEXT_INPUT.toString())) {
357             focusOnIdAfterSubmit = UifConstants.Order.NEXT_INPUT.toString() + ":" + this.getId();
358             submitData.put("focusId", focusOnIdAfterSubmit);
359         } else {
360             // Use the id passed in
361             submitData.put("focusId", focusOnIdAfterSubmit);
362         }
363 
364         // if jump to not set default to jump to location of the action
365         if (StringUtils.isBlank(jumpToIdAfterSubmit) && StringUtils.isBlank(jumpToNameAfterSubmit)) {
366             jumpToIdAfterSubmit = this.getId();
367             submitData.put("jumpToId", jumpToIdAfterSubmit);
368         } else if (StringUtils.isNotBlank(jumpToIdAfterSubmit)) {
369             submitData.put("jumpToId", jumpToIdAfterSubmit);
370         } else {
371             submitData.put("jumpToName", jumpToNameAfterSubmit);
372         }
373 
374         addDataAttribute("submitData", ScriptUtils.toJSON(submitData));
375     }
376 
377     /**
378      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
379      */
380     @Override
381     public List<Component> getComponentsForLifecycle() {
382         List<Component> components = super.getComponentsForLifecycle();
383 
384         components.add(actionImage);
385 
386         return components;
387     }
388 
389     /**
390      * Name of the method that should be called when the action is selected
391      *
392      * <p>
393      * For a server side call (clientSideCall is false), gives the name of the
394      * method in the mapped controller that should be invoked when the action is
395      * selected. For client side calls gives the name of the script function
396      * that should be invoked when the action is selected
397      * </p>
398      *
399      * @return String name of method to call
400      */
401     @BeanTagAttribute(name = "methodToCall")
402     public String getMethodToCall() {
403         return this.methodToCall;
404     }
405 
406     /**
407      * Setter for the actions method to call
408      *
409      * @param methodToCall
410      */
411     public void setMethodToCall(String methodToCall) {
412         this.methodToCall = methodToCall;
413     }
414 
415     /**
416      * Label text for the action
417      *
418      * <p>
419      * The label text is used by the template renderers to give a human readable
420      * label for the action. For buttons this generally is the button text,
421      * while for an action link it would be the links displayed text
422      * </p>
423      *
424      * @return String label for action
425      */
426     @BeanTagAttribute(name = "actionLabel")
427     public String getActionLabel() {
428         return this.actionLabel;
429     }
430 
431     /**
432      * Setter for the actions label
433      *
434      * @param actionLabel
435      */
436     public void setActionLabel(String actionLabel) {
437         this.actionLabel = actionLabel;
438     }
439 
440     /**
441      * Image to use for the action
442      *
443      * <p>
444      * When the action image component is set (and render is true) the image will be
445      * used to present the action as opposed to the default (input submit). For
446      * action link templates the image is used for the link instead of the
447      * action link text
448      * </p>
449      *
450      * @return Image action image
451      */
452     @BeanTagAttribute(name = "actionImage", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
453     public Image getActionImage() {
454         return this.actionImage;
455     }
456 
457     /**
458      * Setter for the action image field
459      *
460      * @param actionImage
461      */
462     public void setActionImage(Image actionImage) {
463         this.actionImage = actionImage;
464     }
465 
466     /**
467      * For an <code>Action</code> that is part of a
468      * <code>NavigationGroup</code, the navigate to page id can be set to
469      * configure the page that should be navigated to when the action is
470      * selected
471      *
472      * <p>
473      * Support exists in the <code>UifControllerBase</code> for handling
474      * navigation between pages
475      * </p>
476      *
477      * @return String id of page that should be rendered when the action item is
478      *         selected
479      */
480     @BeanTagAttribute(name = "navigateToPageId")
481     public String getNavigateToPageId() {
482         return this.navigateToPageId;
483     }
484 
485     /**
486      * Setter for the navigate to page id
487      *
488      * @param navigateToPageId
489      */
490     public void setNavigateToPageId(String navigateToPageId) {
491         this.navigateToPageId = navigateToPageId;
492         actionParameters.put(UifParameters.NAVIGATE_TO_PAGE_ID, navigateToPageId);
493         this.methodToCall = UifConstants.MethodToCallNames.NAVIGATE;
494     }
495 
496     /**
497      * Name of the event that will be set when the action is invoked
498      *
499      * <p>
500      * Action events can be looked at by the view or components in order to render differently depending on
501      * the action requested.
502      * </p>
503      *
504      * @return String action event name
505      * @see org.kuali.rice.krad.uif.UifConstants.ActionEvents
506      */
507     @BeanTagAttribute(name = "actionEvent")
508     public String getActionEvent() {
509         return actionEvent;
510     }
511 
512     /**
513      * Setter for the action event
514      *
515      * @param actionEvent
516      */
517     public void setActionEvent(String actionEvent) {
518         this.actionEvent = actionEvent;
519     }
520 
521     /**
522      * Map of additional data that will be posted when the action is invoked
523      *
524      * <p>
525      * Each entry in this map will be sent as a request parameter when the action is chosen. Note this in
526      * addition to the form data that is sent. For example, suppose the model contained a property named
527      * number and a boolean named showActive, we can send values for this properties by adding the following
528      * entries to this map:
529      * {'number':'a13', 'showActive', 'true'}
530      * </p>
531      *
532      * <p>
533      * The additionalSubmitData map is different from the actionParameters map. All name/value pairs given as
534      * actionParameters populated the form map actionParameters. While name/value pair given in additionalSubmitData
535      * populate different form (model) properties
536      * </p>
537      *
538      * @return Map<String, String> additional key/value pairs to submit
539      */
540     @BeanTagAttribute(name = "additionalSubmitData", type = BeanTagAttribute.AttributeType.MAPVALUE)
541     public Map<String, String> getAdditionalSubmitData() {
542         return additionalSubmitData;
543     }
544 
545     /**
546      * Setter for map holding additional data to post
547      *
548      * @param additionalSubmitData
549      */
550     public void setAdditionalSubmitData(Map<String, String> additionalSubmitData) {
551         this.additionalSubmitData = additionalSubmitData;
552     }
553 
554     /**
555      * Parameters that should be sent when the action is invoked
556      *
557      * <p>
558      * Action renderer will decide how the parameters are sent for the action
559      * (via script generated hiddens, or script parameters, ...)
560      * </p>
561      *
562      * <p>
563      * Can be set by other components such as the <code>CollectionGroup</code>
564      * to provide the context the action is in (such as the collection name and
565      * line the action applies to)
566      * </p>
567      *
568      * @return Map<String, String> action parameters
569      */
570     @BeanTagAttribute(name = "actionParameters", type = BeanTagAttribute.AttributeType.MAPVALUE)
571     public Map<String, String> getActionParameters() {
572         return this.actionParameters;
573     }
574 
575     /**
576      * Setter for the action parameters
577      *
578      * @param actionParameters
579      */
580     public void setActionParameters(Map<String, String> actionParameters) {
581         this.actionParameters = actionParameters;
582     }
583 
584     /**
585      * Convenience method to add a parameter to the action parameters Map
586      *
587      * @param parameterName - name of parameter to add
588      * @param parameterValue - value of parameter to add
589      */
590     public void addActionParameter(String parameterName, String parameterValue) {
591         if (actionParameters == null) {
592             this.actionParameters = new HashMap<String, String>();
593         }
594 
595         this.actionParameters.put(parameterName, parameterValue);
596     }
597 
598     /**
599      * Get an actionParameter by name
600      */
601     public String getActionParameter(String parameterName) {
602         return this.actionParameters.get(parameterName);
603     }
604 
605     /**
606      * Action Security object that indicates what authorization (permissions) exist for the action
607      *
608      * @return ActionSecurity instance
609      */
610     public ActionSecurity getActionSecurity() {
611         return (ActionSecurity) super.getComponentSecurity();
612     }
613 
614     /**
615      * Override to assert a {@link ActionSecurity} instance is set
616      *
617      * @param componentSecurity - instance of ActionSecurity
618      */
619     @Override
620     public void setComponentSecurity(ComponentSecurity componentSecurity) {
621         if (!(componentSecurity instanceof ActionSecurity)) {
622             throw new RiceRuntimeException("Component security for Action should be instance of ActionSecurity");
623         }
624 
625         super.setComponentSecurity(componentSecurity);
626     }
627 
628     @Override
629     protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
630         return ActionSecurity.class;
631     }
632 
633     /**
634      * @return the jumpToIdAfterSubmit
635      */
636     @BeanTagAttribute(name = "jumpToIdAfterSubmit")
637     public String getJumpToIdAfterSubmit() {
638         return this.jumpToIdAfterSubmit;
639     }
640 
641     /**
642      * The id to jump to in the next page, the element with this id will be
643      * jumped to automatically when the new page is retrieved after a submit.
644      * Using "TOP" or "BOTTOM" will jump to the top or the bottom of the
645      * resulting page. Passing in nothing for both jumpToIdAfterSubmit and
646      * jumpToNameAfterSubmit will result in this Action being jumped to by
647      * default if it is present on the new page. WARNING: jumpToIdAfterSubmit
648      * always takes precedence over jumpToNameAfterSubmit, if set.
649      *
650      * @param jumpToIdAfterSubmit the jumpToIdAfterSubmit to set
651      */
652     public void setJumpToIdAfterSubmit(String jumpToIdAfterSubmit) {
653         this.jumpToIdAfterSubmit = jumpToIdAfterSubmit;
654     }
655 
656     /**
657      * The name to jump to in the next page, the element with this name will be
658      * jumped to automatically when the new page is retrieved after a submit.
659      * Passing in nothing for both jumpToIdAfterSubmit and jumpToNameAfterSubmit
660      * will result in this Action being jumped to by default if it is
661      * present on the new page. WARNING: jumpToIdAfterSubmit always takes
662      * precedence over jumpToNameAfterSubmit, if set.
663      *
664      * @return the jumpToNameAfterSubmit
665      */
666     @BeanTagAttribute(name = "jumpToNameAfterSubmit")
667     public String getJumpToNameAfterSubmit() {
668         return this.jumpToNameAfterSubmit;
669     }
670 
671     /**
672      * @param jumpToNameAfterSubmit the jumpToNameAfterSubmit to set
673      */
674     public void setJumpToNameAfterSubmit(String jumpToNameAfterSubmit) {
675         this.jumpToNameAfterSubmit = jumpToNameAfterSubmit;
676     }
677 
678     /**
679      * The element to place focus on in the new page after the new page
680      * is retrieved.
681      *
682      * <p>The following are allowed:
683      * <ul>
684      * <li>A valid element id</li>
685      * <li>"FIRST" will focus on the first visible input element on the form</li>
686      * <li>"SELF" will result in this Action being focused (action bean defaults to "SELF")</li>
687      * <li>"LINE_FIRST" will result in the first input of the collection line to be focused (if available)</li>
688      * <li>"NEXT_INPUT" will result in the next available input that exists after this Action to be focused
689      * (only if this action still exists on the page)</li>
690      * </ul>
691      * </p>
692      *
693      * @return the focusOnAfterSubmit
694      */
695     @BeanTagAttribute(name = "focusOnIdAfterSubmit")
696     public String getFocusOnIdAfterSubmit() {
697         return this.focusOnIdAfterSubmit;
698     }
699 
700     /**
701      * @param focusOnIdAfterSubmit the focusOnAfterSubmit to set
702      */
703     public void setFocusOnIdAfterSubmit(String focusOnIdAfterSubmit) {
704         this.focusOnIdAfterSubmit = focusOnIdAfterSubmit;
705     }
706 
707     /**
708      * Indicates whether the form data should be validated on the client side
709      *
710      * return true if validation should occur, false otherwise
711      */
712     @BeanTagAttribute(name = "performClientSideValidation")
713     public boolean isPerformClientSideValidation() {
714         return this.performClientSideValidation;
715     }
716 
717     /**
718      * Setter for the client side validation flag
719      *
720      * @param performClientSideValidation
721      */
722     public void setPerformClientSideValidation(boolean performClientSideValidation) {
723         this.performClientSideValidation = performClientSideValidation;
724     }
725 
726     /**
727      * Client side javascript to be executed when this actionField is clicked
728      *
729      * <p>
730      * This overrides the default action for this Action so the method
731      * called must explicitly submit, navigate, etc. through js, if necessary.
732      * In addition, this js occurs AFTER onClickScripts set on this field, it
733      * will be the last script executed by the click event. Sidenote: This js is
734      * always called after hidden actionParameters and methodToCall methods are
735      * written by the js to the html form.
736      * </p>
737      *
738      * @return the actionScript
739      */
740     @BeanTagAttribute(name = "actionScript")
741     public String getActionScript() {
742         return this.actionScript;
743     }
744 
745     /**
746      * @param actionScript the actionScript to set
747      */
748     public void setActionScript(String actionScript) {
749         if (!StringUtils.endsWith(actionScript, ";")) {
750             actionScript = actionScript + ";";
751         }
752         this.actionScript = actionScript;
753     }
754 
755     /**
756      * @param performDirtyValidation the blockValidateDirty to set
757      */
758     public void setPerformDirtyValidation(boolean performDirtyValidation) {
759         this.performDirtyValidation = performDirtyValidation;
760     }
761 
762     /**
763      * @return the blockValidateDirty
764      */
765     @BeanTagAttribute(name = "performDirtyValidation")
766     public boolean isPerformDirtyValidation() {
767         return performDirtyValidation;
768     }
769 
770     /**
771      * Indicates whether the action (input or button) is disabled (doesn't allow interaction)
772      *
773      * @return boolean true if the action field is disabled, false if not
774      */
775     @BeanTagAttribute(name = "disabled")
776     public boolean isDisabled() {
777         return disabled;
778     }
779 
780     /**
781      * Setter for the disabled indicator
782      *
783      * @param disabled
784      */
785     public void setDisabled(boolean disabled) {
786         this.disabled = disabled;
787     }
788 
789     /**
790      * If the action field is disabled, gives a reason for why which will be displayed as a tooltip
791      * on the action field (button)
792      *
793      * @return String disabled reason text
794      * @see #isDisabled()
795      */
796     @BeanTagAttribute(name = "disabledReason")
797     public String getDisabledReason() {
798         return disabledReason;
799     }
800 
801     /**
802      * Setter for the disabled reason text
803      *
804      * @param disabledReason
805      */
806     public void setDisabledReason(String disabledReason) {
807         this.disabledReason = disabledReason;
808     }
809 
810     @BeanTagAttribute(name = "actionImagePlacement")
811     public String getActionImagePlacement() {
812         return actionImagePlacement;
813     }
814 
815     /**
816      * Set to TOP, BOTTOM, LEFT, RIGHT to position image at that location within the button.
817      * For the subclass ActionLinkField only LEFT and RIGHT are allowed.  When set to blank/null/IMAGE_ONLY, the image
818      * itself will be the Action, if no value is set the default is ALWAYS LEFT, you must explicitly set
819      * blank/null/IMAGE_ONLY to use ONLY the image as the Action.
820      *
821      * @return
822      */
823     public void setActionImagePlacement(String actionImagePlacement) {
824         this.actionImagePlacement = actionImagePlacement;
825     }
826 
827     /**
828      * Gets the script which needs to be invoked before the form is submitted
829      *
830      * <p>
831      * The preSubmitCall can carry out custom logic for the action before the submit occurs. The value should
832      * be given as one or more lines of script and should return a boolean. If false is returned from the call,
833      * the submit is not carried out. Furthermore, the preSubmitCall can refer to the request object through the
834      * variable 'kradRequest' or 'this'. This gives full access over the request for doing such things as
835      * adding additional data
836      * </p>
837      *
838      * <p>
839      * Examples 'return doFunction(kradRequest);', 'var valid=true;return valid;'
840      * </p>
841      *
842      * <p>
843      * The preSubmit call will be invoked both for ajax and non-ajax submits
844      * </p>
845      *
846      * @return String script text that will be invoked before form submission
847      */
848     @BeanTagAttribute(name = "preSubmitCall")
849     public String getPreSubmitCall() {
850         return preSubmitCall;
851     }
852 
853     /**
854      * Setter for preSubmitCall
855      *
856      * @param preSubmitCall
857      */
858     public void setPreSubmitCall(String preSubmitCall) {
859         this.preSubmitCall = preSubmitCall;
860     }
861 
862     /**
863      * When this property is set to true it will submit the form using Ajax instead of the browser submit. Will default
864      * to updating the page contents
865      *
866      * @return boolean
867      */
868     @BeanTagAttribute(name = "ajaxSubmit")
869     public boolean isAjaxSubmit() {
870         return ajaxSubmit;
871     }
872 
873     /**
874      * Setter for ajaxSubmit
875      *
876      * @param ajaxSubmit
877      */
878     public void setAjaxSubmit(boolean ajaxSubmit) {
879         this.ajaxSubmit = ajaxSubmit;
880     }
881 
882     /**
883      * Gets the return type for the ajax call
884      *
885      * <p>
886      * The ajax return type indicates how the response content will be handled in the client. Typical
887      * examples include updating a component, the page, or doing a redirect.
888      * </p>
889      *
890      * @return String return type
891      * @see org.kuali.rice.krad.uif.UifConstants.AjaxReturnTypes
892      */
893     @BeanTagAttribute(name = "ajaxReturnType")
894     public String getAjaxReturnType() {
895         return this.ajaxReturnType;
896     }
897 
898     /**
899      * Setter for the type of ajax return
900      *
901      * @param ajaxReturnType
902      */
903     public void setAjaxReturnType(String ajaxReturnType) {
904         this.ajaxReturnType = ajaxReturnType;
905     }
906 
907     /**
908      * Indicates if the action response should be displayed in a lightbox
909      *
910      * @return boolean true if response should be rendered in a lightbox, false if not
911      */
912     @BeanTagAttribute(name = "displayResponseInLightBox")
913     public boolean isDisplayResponseInLightBox() {
914         return StringUtils.equals(this.ajaxReturnType, UifConstants.AjaxReturnTypes.DISPLAYLIGHTBOX.getKey());
915     }
916 
917     /**
918      * Setter for indicating the response should be rendered in a lightbox
919      *
920      * @param displayResponseInLightBox
921      */
922     public void setDisplayResponseInLightBox(boolean displayResponseInLightBox) {
923         if (displayResponseInLightBox) {
924             this.ajaxReturnType = UifConstants.AjaxReturnTypes.DISPLAYLIGHTBOX.getKey();
925         }
926         // if display lightbox is false and it was previously true, set to default of update page
927         else if (StringUtils.equals(this.ajaxReturnType, UifConstants.AjaxReturnTypes.DISPLAYLIGHTBOX.getKey())) {
928             this.ajaxReturnType = UifConstants.AjaxReturnTypes.UPDATEPAGE.getKey();
929         }
930     }
931 
932     /**
933      * Gets the script which will be invoked on a successful ajax call
934      *
935      * <p>
936      * The successCallback can carry out custom logic after a successful ajax submission has been made. The
937      * value can contain one or more script statements. In addition, the response contents can be accessed
938      * through the variable 'responseContents'
939      * </p>
940      *
941      * <p>
942      * Examples 'handleSuccessfulUpdate(responseContents);'
943      * </p>
944      *
945      * <p>
946      * The successCallback may only be specified when {@link #isAjaxSubmit()} is true
947      * </p>
948      *
949      * @return String containing script to be executed when the action is successful
950      */
951     @BeanTagAttribute(name = "successCallback")
952     public String getSuccessCallback() {
953         return successCallback;
954     }
955 
956     /**
957      * Setter for successCallback
958      *
959      * @param successCallback
960      */
961     public void setSuccessCallback(String successCallback) {
962         this.successCallback = successCallback;
963     }
964 
965     /**
966      * Gets the script which will be invoked when the action fails due to problems in the ajax call or
967      * the return of an incident report
968      *
969      * <p>
970      * The errorCallback can carry out custom logic after a failed ajax submission. The
971      * value can contain one or more script statements. In addition, the response contents can be accessed
972      * through the variable 'responseContents'
973      * </p>
974      *
975      * <p>
976      * Examples 'handleFailedUpdate(responseContents);'
977      * </p>
978      *
979      * <p>
980      * The errorCallback may only be specified when {@link #isAjaxSubmit()} is true
981      * </p>
982      *
983      * @return String containing script to be executed when the action is successful
984      */
985     @BeanTagAttribute(name = "errorCallback")
986     public String getErrorCallback() {
987         return errorCallback;
988     }
989 
990     /**
991      * Setter for errorCallback
992      *
993      * @param errorCallback
994      */
995     public void setErrorCallback(String errorCallback) {
996         this.errorCallback = errorCallback;
997     }
998 
999     /**
1000      * Id for the component that should be refreshed after the action completes
1001      *
1002      * <p>
1003      * Either refresh id or refresh property name can be set to configure the component that should
1004      * be refreshed after the action completes. If both are blank, the page will be refreshed
1005      * </p>
1006      *
1007      * @return String valid component id
1008      */
1009     @BeanTagAttribute(name = "refreshId")
1010     public String getRefreshId() {
1011         return refreshId;
1012     }
1013 
1014     /**
1015      * Setter for the component refresh id
1016      *
1017      * @param refreshId
1018      */
1019     public void setRefreshId(String refreshId) {
1020         this.refreshId = refreshId;
1021     }
1022 
1023     /**
1024      * Property name for the {@link org.kuali.rice.krad.uif.field.DataField} that should be refreshed after the action
1025      * completes
1026      *
1027      * <p>
1028      * Either refresh id or refresh property name can be set to configure the component that should
1029      * be refreshed after the action completes. If both are blank, the page will be refreshed
1030      * </p>
1031      *
1032      * <p>
1033      * Property name will be adjusted to use the default binding path unless it contains the form prefix
1034      * </p>
1035      *
1036      * @return String valid property name with an associated DataField
1037      * @see org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX
1038      */
1039     @BeanTagAttribute(name = "refreshPropertyName")
1040     public String getRefreshPropertyName() {
1041         return refreshPropertyName;
1042     }
1043 
1044     /**
1045      * Setter for the property name of the DataField that should be refreshed
1046      *
1047      * @param refreshPropertyName
1048      */
1049     public void setRefreshPropertyName(String refreshPropertyName) {
1050         this.refreshPropertyName = refreshPropertyName;
1051     }
1052 
1053     /**
1054      * Gets the loading message used by action's blockUI
1055      *
1056      * @returns String if String is not null, used in place of loading message
1057      */
1058     @BeanTagAttribute(name = "loadingMessageText")
1059     public String getLoadingMessageText() {
1060         return loadingMessageText;
1061     }
1062 
1063     /**
1064      * When this property is set, it is used in place of the loading message text used by the blockUI
1065      *
1066      * @param loadingMessageText
1067      */
1068     public void setLoadingMessageText(String loadingMessageText) {
1069         this.loadingMessageText = loadingMessageText;
1070     }
1071 
1072     /**
1073      * Indicates whether blocking for the action should be disabled
1074      *
1075      * <p>
1076      * By default when an action is invoked part of the page or the entire window is blocked until
1077      * the action completes. If this property is set to true the blocking will not be displayed.
1078      * </p>
1079      *
1080      * <p>
1081      * Currently if an action returns a file download, this property should be set to true. If not, the blocking
1082      * will never get unblocked (because the page does not get notification a file was downloaded)
1083      * </p>
1084      *
1085      * @return boolean true if blocking should be disabled, false if not
1086      */
1087     @BeanTagAttribute(name = "disableBlocking")
1088     public boolean isDisableBlocking() {
1089         return disableBlocking;
1090     }
1091 
1092     /**
1093      * Setter for disabling blocking when the action is invoked
1094      *
1095      * @param disableBlocking
1096      */
1097     public void setDisableBlocking(boolean disableBlocking) {
1098         this.disableBlocking = disableBlocking;
1099     }
1100 
1101     /**
1102      * @see org.kuali.rice.krad.uif.component.Component#completeValidation
1103      */
1104     @Override
1105     public void completeValidation(ValidationTrace tracer) {
1106         tracer.addBean(this);
1107 
1108         // Checks that a label or image ui is presence
1109         if (getActionLabel() == null && getActionImage() == null) {
1110             String currentValues[] = {"actionLabel =" + getActionLabel(), "actionImage =" + getActionImage()};
1111             tracer.createError("ActionLabel and/or actionImage must be set", currentValues);
1112         }
1113 
1114         // Checks that an action is set
1115         if (getJumpToIdAfterSubmit() != null && getJumpToNameAfterSubmit() != null) {
1116             String currentValues[] = {"jumpToIdAfterSubmit =" + getJumpToIdAfterSubmit(),
1117                     "jumpToNameAfterSubmit =" + getJumpToNameAfterSubmit()};
1118             tracer.createWarning("Only 1 jumpTo property should be set", currentValues);
1119         }
1120         super.completeValidation(tracer.getCopy());
1121     }
1122 
1123     /**
1124      * Evaluate the disable condition on controls which disable it on each key up event
1125      *
1126      * @return true if evaluate on key up, false otherwise
1127      */
1128     @BeanTagAttribute(name = "evaluateDisabledOnKeyUp")
1129     public boolean isEvaluateDisabledOnKeyUp() {
1130         return evaluateDisabledOnKeyUp;
1131     }
1132 
1133     /**
1134      * Set evaluateDisableOnKeyUp
1135      *
1136      * @param evaluateDisabledOnKeyUp
1137      */
1138     public void setEvaluateDisabledOnKeyUp(boolean evaluateDisabledOnKeyUp) {
1139         this.evaluateDisabledOnKeyUp = evaluateDisabledOnKeyUp;
1140     }
1141 
1142     /**
1143      * Get the disable condition js derived from the springEL, cannot be set.
1144      *
1145      * @return the disableConditionJs javascript to be evaluated
1146      */
1147     public String getDisabledConditionJs() {
1148         return disabledConditionJs;
1149     }
1150 
1151     /**
1152      * Control names to add handlers to for disable functionality, cannot be set
1153      *
1154      * @return control names to add handlers to for disable
1155      */
1156     public List<String> getDisabledConditionControlNames() {
1157         return disabledConditionControlNames;
1158     }
1159 
1160     /**
1161      * Gets the property names of fields that when changed, will disable this component
1162      *
1163      * @return the property names to monitor for change to disable this component
1164      */
1165     @BeanTagAttribute(name = "disabledWhenChangedPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
1166     public List<String> getDisabledWhenChangedPropertyNames() {
1167         return disabledWhenChangedPropertyNames;
1168     }
1169 
1170     /**
1171      * Sets the property names of fields that when changed, will disable this component
1172      *
1173      * @param disabledWhenChangedPropertyNames
1174      */
1175     public void setDisabledWhenChangedPropertyNames(List<String> disabledWhenChangedPropertyNames) {
1176         this.disabledWhenChangedPropertyNames = disabledWhenChangedPropertyNames;
1177     }
1178 
1179     /**
1180      * Gets the property names of fields that when changed, will enable this component
1181      *
1182      * @return the property names to monitor for change to enable this component
1183      */
1184     @BeanTagAttribute(name = "enabledWhenChangedPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
1185     public List<String> getEnabledWhenChangedPropertyNames() {
1186         return enabledWhenChangedPropertyNames;
1187     }
1188 
1189     /**
1190      * Sets the property names of fields that when changed, will enable this component
1191      *
1192      * @param enabledWhenChangedPropertyNames
1193      */
1194     public void setEnabledWhenChangedPropertyNames(List<String> enabledWhenChangedPropertyNames) {
1195         this.enabledWhenChangedPropertyNames = enabledWhenChangedPropertyNames;
1196     }
1197 }