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.service.ExpressionEvaluatorService;
32  import org.kuali.rice.krad.uif.util.ExpressionUtils;
33  import org.kuali.rice.krad.uif.util.ScriptUtils;
34  import org.kuali.rice.krad.uif.view.FormView;
35  import org.kuali.rice.krad.uif.view.View;
36  import org.kuali.rice.krad.util.KRADUtils;
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 = "backAction-bean", parent = "Uif-BackAction"),
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         //on click script becomes a data attribute for use in a global handler on the client
257         this.addDataAttribute(UifConstants.DataAttributes.ONCLICK, KRADUtils.convertToHTMLAttributeSafeString(
258                 "e.preventDefault();" + onClickScript));
259     }
260 
261     /**
262      * When the action is updating a component sets up the refresh script for the component (found by the
263      * given refresh id or refresh property name)
264      *
265      * @param view view instance the action belongs to
266      */
267     protected void setupRefreshAction(View view) {
268         // if refresh property or id is given, make return type update component
269         // TODO: what if the refresh id is the page id? we should set the return type as update page
270         if (StringUtils.isNotBlank(refreshPropertyName) || StringUtils.isNotBlank(refreshId)) {
271             ajaxReturnType = UifConstants.AjaxReturnTypes.UPDATECOMPONENT.getKey();
272         }
273 
274         // if refresh property name is given, adjust the binding and then attempt to find the
275         // component in the view index
276         Component refreshComponent = null;
277         if (StringUtils.isNotBlank(refreshPropertyName)) {
278             // TODO: does this support all binding prefixes?
279             if (refreshPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
280                 refreshPropertyName = StringUtils.removeStart(refreshPropertyName, UifConstants.NO_BIND_ADJUST_PREFIX);
281             } else if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) {
282                 refreshPropertyName = view.getDefaultBindingObjectPath() + "." + refreshPropertyName;
283             }
284 
285             DataField dataField = view.getViewIndex().getDataFieldByPath(refreshPropertyName);
286             if (dataField != null) {
287                 refreshComponent = dataField;
288                 refreshId = refreshComponent.getId();
289             }
290         } else if (StringUtils.isNotBlank(refreshId)) {
291             Component component = view.getViewIndex().getComponentById(refreshId);
292             if (component != null) {
293                 refreshComponent = component;
294             }
295         }
296 
297         if (refreshComponent != null) {
298             refreshComponent.setRefreshedByAction(true);
299 
300             // update initial state
301             Component initialComponent = view.getViewIndex().getInitialComponentStates().get(
302                     refreshComponent.getBaseId());
303             if (initialComponent != null) {
304                 initialComponent.setRefreshedByAction(true);
305                 view.getViewIndex().getInitialComponentStates().put(refreshComponent.getBaseId(), initialComponent);
306             }
307         }
308     }
309 
310     /**
311      * Builds the data attributes that will be read client side to determine how to
312      * handle the action and the additional data that should be submitted with the action
313      *
314      * <p>
315      * Note these data attributes will be exposed as a data map client side. The simple attributes (non object
316      * value) are also written out as attributes on the action element.
317      * </p>
318      *
319      * @param view view instance the action belongs to
320      * @param model model object containing the view data
321      * @param parent component the holds the action
322      */
323     protected void buildActionData(View view, Object model, Component parent) {
324         // map properties to data attributes
325         addDataAttribute("ajaxsubmit", Boolean.toString(ajaxSubmit));
326         addDataAttributeIfNonEmpty("successcallback", this.successCallback);
327         addDataAttributeIfNonEmpty("errorcallback", this.errorCallback);
328         addDataAttributeIfNonEmpty("presubmitcall", this.preSubmitCall);
329         addDataAttributeIfNonEmpty("loadingmessage", this.loadingMessageText);
330         addDataAttributeIfNonEmpty("disableblocking", Boolean.toString(this.disableBlocking));
331         addDataAttributeIfNonEmpty("ajaxreturntype", this.ajaxReturnType);
332         addDataAttributeIfNonEmpty("refreshid", this.refreshId);
333         addDataAttribute("validate", Boolean.toString(this.performClientSideValidation));
334 
335         // all action parameters should be submitted with action
336         Map<String, String> submitData = new HashMap<String, String>();
337         for (String key : actionParameters.keySet()) {
338             String parameterPath = key;
339             if (!key.equals(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME)) {
340                 parameterPath = UifPropertyPaths.ACTION_PARAMETERS + "[" + key + "]";
341             }
342             submitData.put(parameterPath, actionParameters.get(key));
343         }
344 
345         for (String key : additionalSubmitData.keySet()) {
346             submitData.put(key, additionalSubmitData.get(key));
347         }
348 
349         // TODO possibly fix some other way - this is a workaround, prevents
350         // showing history and showing home again on actions which submit the form
351         submitData.put(UifConstants.UrlParams.SHOW_HISTORY, "false");
352         submitData.put(UifConstants.UrlParams.SHOW_HOME, "false");
353 
354         // if focus id not set default to focus on action
355         if (focusOnIdAfterSubmit == null){
356             focusOnIdAfterSubmit = UifConstants.Order.SELF.toString();
357         }
358 
359         if (focusOnIdAfterSubmit.equalsIgnoreCase(UifConstants.Order.SELF.toString())) {
360             focusOnIdAfterSubmit = this.getId();
361             submitData.put("focusId", focusOnIdAfterSubmit);
362         } else if (focusOnIdAfterSubmit.equalsIgnoreCase(UifConstants.Order.NEXT_INPUT.toString())) {
363             focusOnIdAfterSubmit = UifConstants.Order.NEXT_INPUT.toString() + ":" + this.getId();
364             submitData.put("focusId", focusOnIdAfterSubmit);
365         } else {
366             // Use the id passed in
367             submitData.put("focusId", focusOnIdAfterSubmit);
368         }
369 
370         // if jump to not set default to jump to location of the action
371         if (StringUtils.isBlank(jumpToIdAfterSubmit) && StringUtils.isBlank(jumpToNameAfterSubmit)) {
372             jumpToIdAfterSubmit = this.getId();
373             submitData.put("jumpToId", jumpToIdAfterSubmit);
374         } else if (StringUtils.isNotBlank(jumpToIdAfterSubmit)) {
375             submitData.put("jumpToId", jumpToIdAfterSubmit);
376         } else {
377             submitData.put("jumpToName", jumpToNameAfterSubmit);
378         }
379 
380         addDataAttribute(UifConstants.DataAttributes.SUBMIT_DATA, ScriptUtils.toJSON(submitData));
381     }
382 
383     /**
384      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
385      */
386     @Override
387     public List<Component> getComponentsForLifecycle() {
388         List<Component> components = super.getComponentsForLifecycle();
389 
390         components.add(actionImage);
391 
392         return components;
393     }
394 
395     /**
396      * Name of the method that should be called when the action is selected
397      *
398      * <p>
399      * For a server side call (clientSideCall is false), gives the name of the
400      * method in the mapped controller that should be invoked when the action is
401      * selected. For client side calls gives the name of the script function
402      * that should be invoked when the action is selected
403      * </p>
404      *
405      * @return name of method to call
406      */
407     @BeanTagAttribute(name = "methodToCall")
408     public String getMethodToCall() {
409         return this.methodToCall;
410     }
411 
412     /**
413      * Setter for the actions method to call
414      *
415      * @param methodToCall
416      */
417     public void setMethodToCall(String methodToCall) {
418         this.methodToCall = methodToCall;
419     }
420 
421     /**
422      * Label text for the action
423      *
424      * <p>
425      * The label text is used by the template renderers to give a human readable
426      * label for the action. For buttons this generally is the button text,
427      * while for an action link it would be the links displayed text
428      * </p>
429      *
430      * @return label for action
431      */
432     @BeanTagAttribute(name = "actionLabel")
433     public String getActionLabel() {
434         return this.actionLabel;
435     }
436 
437     /**
438      * Setter for the actions label
439      *
440      * @param actionLabel
441      */
442     public void setActionLabel(String actionLabel) {
443         this.actionLabel = actionLabel;
444     }
445 
446     /**
447      * Image to use for the action
448      *
449      * <p>
450      * When the action image component is set (and render is true) the image will be
451      * used to present the action as opposed to the default (input submit). For
452      * action link templates the image is used for the link instead of the
453      * action link text
454      * </p>
455      *
456      * @return action image
457      */
458     @BeanTagAttribute(name = "actionImage", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
459     public Image getActionImage() {
460         return this.actionImage;
461     }
462 
463     /**
464      * Setter for the action image field
465      *
466      * @param actionImage
467      */
468     public void setActionImage(Image actionImage) {
469         this.actionImage = actionImage;
470     }
471 
472     /**
473      * For an <code>Action</code> that is part of a
474      * <code>NavigationGroup</code, the navigate to page id can be set to
475      * configure the page that should be navigated to when the action is
476      * selected
477      *
478      * <p>
479      * Support exists in the <code>UifControllerBase</code> for handling
480      * navigation between pages
481      * </p>
482      *
483      * @return id of page that should be rendered when the action item is
484      *         selected
485      */
486     @BeanTagAttribute(name = "navigateToPageId")
487     public String getNavigateToPageId() {
488         return this.navigateToPageId;
489     }
490 
491     /**
492      * Setter for the navigate to page id
493      *
494      * @param navigateToPageId
495      */
496     public void setNavigateToPageId(String navigateToPageId) {
497         this.navigateToPageId = navigateToPageId;
498         actionParameters.put(UifParameters.NAVIGATE_TO_PAGE_ID, navigateToPageId);
499         this.methodToCall = UifConstants.MethodToCallNames.NAVIGATE;
500     }
501 
502     /**
503      * Name of the event that will be set when the action is invoked
504      *
505      * <p>
506      * Action events can be looked at by the view or components in order to render differently depending on
507      * the action requested.
508      * </p>
509      *
510      * @return action event name
511      * @see org.kuali.rice.krad.uif.UifConstants.ActionEvents
512      */
513     @BeanTagAttribute(name = "actionEvent")
514     public String getActionEvent() {
515         return actionEvent;
516     }
517 
518     /**
519      * Setter for the action event
520      *
521      * @param actionEvent
522      */
523     public void setActionEvent(String actionEvent) {
524         this.actionEvent = actionEvent;
525     }
526 
527     /**
528      * Map of additional data that will be posted when the action is invoked
529      *
530      * <p>
531      * Each entry in this map will be sent as a request parameter when the action is chosen. Note this in
532      * addition to the form data that is sent. For example, suppose the model contained a property named
533      * number and a boolean named showActive, we can send values for this properties by adding the following
534      * entries to this map:
535      * {'number':'a13', 'showActive', 'true'}
536      * </p>
537      *
538      * <p>
539      * The additionalSubmitData map is different from the actionParameters map. All name/value pairs given as
540      * actionParameters populated the form map actionParameters. While name/value pair given in additionalSubmitData
541      * populate different form (model) properties
542      * </p>
543      *
544      * @return additional key/value pairs to submit
545      */
546     @BeanTagAttribute(name = "additionalSubmitData", type = BeanTagAttribute.AttributeType.MAPVALUE)
547     public Map<String, String> getAdditionalSubmitData() {
548         return additionalSubmitData;
549     }
550 
551     /**
552      * Setter for map holding additional data to post
553      *
554      * @param additionalSubmitData
555      */
556     public void setAdditionalSubmitData(Map<String, String> additionalSubmitData) {
557         this.additionalSubmitData = additionalSubmitData;
558     }
559 
560     /**
561      * Parameters that should be sent when the action is invoked
562      *
563      * <p>
564      * Action renderer will decide how the parameters are sent for the action
565      * (via script generated hiddens, or script parameters, ...)
566      * </p>
567      *
568      * <p>
569      * Can be set by other components such as the <code>CollectionGroup</code>
570      * to provide the context the action is in (such as the collection name and
571      * line the action applies to)
572      * </p>
573      *
574      * @return action parameters
575      */
576     @BeanTagAttribute(name = "actionParameters", type = BeanTagAttribute.AttributeType.MAPVALUE)
577     public Map<String, String> getActionParameters() {
578         return this.actionParameters;
579     }
580 
581     /**
582      * Setter for the action parameters
583      *
584      * @param actionParameters
585      */
586     public void setActionParameters(Map<String, String> actionParameters) {
587         this.actionParameters = actionParameters;
588     }
589 
590     /**
591      * Convenience method to add a parameter to the action parameters Map
592      *
593      * @param parameterName name of parameter to add
594      * @param parameterValue value of parameter to add
595      */
596     public void addActionParameter(String parameterName, String parameterValue) {
597         if (actionParameters == null) {
598             this.actionParameters = new HashMap<String, String>();
599         }
600 
601         this.actionParameters.put(parameterName, parameterValue);
602     }
603 
604     /**
605      * Get an actionParameter by name
606      */
607     public String getActionParameter(String parameterName) {
608         return this.actionParameters.get(parameterName);
609     }
610 
611     /**
612      * Action Security object that indicates what authorization (permissions) exist for the action
613      *
614      * @return ActionSecurity instance
615      */
616     public ActionSecurity getActionSecurity() {
617         return (ActionSecurity) super.getComponentSecurity();
618     }
619 
620     /**
621      * Override to assert a {@link ActionSecurity} instance is set
622      *
623      * @param componentSecurity instance of ActionSecurity
624      */
625     @Override
626     public void setComponentSecurity(ComponentSecurity componentSecurity) {
627         if (!(componentSecurity instanceof ActionSecurity)) {
628             throw new RiceRuntimeException("Component security for Action should be instance of ActionSecurity");
629         }
630 
631         super.setComponentSecurity(componentSecurity);
632     }
633 
634     @Override
635     protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
636         return ActionSecurity.class;
637     }
638 
639     /**
640      * @return the jumpToIdAfterSubmit
641      */
642     @BeanTagAttribute(name = "jumpToIdAfterSubmit")
643     public String getJumpToIdAfterSubmit() {
644         return this.jumpToIdAfterSubmit;
645     }
646 
647     /**
648      * The id to jump to in the next page, the element with this id will be
649      * jumped to automatically when the new page is retrieved after a submit.
650      * Using "TOP" or "BOTTOM" will jump to the top or the bottom of the
651      * resulting page. Passing in nothing for both jumpToIdAfterSubmit and
652      * jumpToNameAfterSubmit will result in this Action being jumped to by
653      * default if it is present on the new page. WARNING: jumpToIdAfterSubmit
654      * always takes precedence over jumpToNameAfterSubmit, if set.
655      *
656      * @param jumpToIdAfterSubmit the jumpToIdAfterSubmit to set
657      */
658     public void setJumpToIdAfterSubmit(String jumpToIdAfterSubmit) {
659         this.jumpToIdAfterSubmit = jumpToIdAfterSubmit;
660     }
661 
662     /**
663      * The name to jump to in the next page, the element with this name will be
664      * jumped to automatically when the new page is retrieved after a submit.
665      * Passing in nothing for both jumpToIdAfterSubmit and jumpToNameAfterSubmit
666      * will result in this Action being jumped to by default if it is
667      * present on the new page. WARNING: jumpToIdAfterSubmit always takes
668      * precedence over jumpToNameAfterSubmit, if set.
669      *
670      * @return the jumpToNameAfterSubmit
671      */
672     @BeanTagAttribute(name = "jumpToNameAfterSubmit")
673     public String getJumpToNameAfterSubmit() {
674         return this.jumpToNameAfterSubmit;
675     }
676 
677     /**
678      * @param jumpToNameAfterSubmit the jumpToNameAfterSubmit to set
679      */
680     public void setJumpToNameAfterSubmit(String jumpToNameAfterSubmit) {
681         this.jumpToNameAfterSubmit = jumpToNameAfterSubmit;
682     }
683 
684     /**
685      * The element to place focus on in the new page after the new page
686      * is retrieved.
687      *
688      * <p>The following are allowed:
689      * <ul>
690      * <li>A valid element id</li>
691      * <li>"FIRST" will focus on the first visible input element on the form</li>
692      * <li>"SELF" will result in this Action being focused (action bean defaults to "SELF")</li>
693      * <li>"LINE_FIRST" will result in the first input of the collection line to be focused (if available)</li>
694      * <li>"NEXT_INPUT" will result in the next available input that exists after this Action to be focused
695      * (only if this action still exists on the page)</li>
696      * </ul>
697      * </p>
698      *
699      * @return the focusOnAfterSubmit
700      */
701     @BeanTagAttribute(name = "focusOnIdAfterSubmit")
702     public String getFocusOnIdAfterSubmit() {
703         return this.focusOnIdAfterSubmit;
704     }
705 
706     /**
707      * @param focusOnIdAfterSubmit the focusOnAfterSubmit to set
708      */
709     public void setFocusOnIdAfterSubmit(String focusOnIdAfterSubmit) {
710         this.focusOnIdAfterSubmit = focusOnIdAfterSubmit;
711     }
712 
713     /**
714      * Indicates whether the form data should be validated on the client side
715      *
716      * return true if validation should occur, false otherwise
717      */
718     @BeanTagAttribute(name = "performClientSideValidation")
719     public boolean isPerformClientSideValidation() {
720         return this.performClientSideValidation;
721     }
722 
723     /**
724      * Setter for the client side validation flag
725      *
726      * @param performClientSideValidation
727      */
728     public void setPerformClientSideValidation(boolean performClientSideValidation) {
729         this.performClientSideValidation = performClientSideValidation;
730     }
731 
732     /**
733      * Client side javascript to be executed when this actionField is clicked
734      *
735      * <p>
736      * This overrides the default action for this Action so the method
737      * called must explicitly submit, navigate, etc. through js, if necessary.
738      * In addition, this js occurs AFTER onClickScripts set on this field, it
739      * will be the last script executed by the click event. Sidenote: This js is
740      * always called after hidden actionParameters and methodToCall methods are
741      * written by the js to the html form.
742      * </p>
743      *
744      * @return the actionScript
745      */
746     @BeanTagAttribute(name = "actionScript")
747     public String getActionScript() {
748         return this.actionScript;
749     }
750 
751     /**
752      * @param actionScript the actionScript to set
753      */
754     public void setActionScript(String actionScript) {
755         if (!StringUtils.endsWith(actionScript, ";")) {
756             actionScript = actionScript + ";";
757         }
758         this.actionScript = actionScript;
759     }
760 
761     /**
762      * @param performDirtyValidation the blockValidateDirty to set
763      */
764     public void setPerformDirtyValidation(boolean performDirtyValidation) {
765         this.performDirtyValidation = performDirtyValidation;
766     }
767 
768     /**
769      * @return the blockValidateDirty
770      */
771     @BeanTagAttribute(name = "performDirtyValidation")
772     public boolean isPerformDirtyValidation() {
773         return performDirtyValidation;
774     }
775 
776     /**
777      * Indicates whether the action (input or button) is disabled (doesn't allow interaction)
778      *
779      * @return true if the action field is disabled, false if not
780      */
781     @BeanTagAttribute(name = "disabled")
782     public boolean isDisabled() {
783         return disabled;
784     }
785 
786     /**
787      * Setter for the disabled indicator
788      *
789      * @param disabled
790      */
791     public void setDisabled(boolean disabled) {
792         this.disabled = disabled;
793     }
794 
795     /**
796      * If the action field is disabled, gives a reason for why which will be displayed as a tooltip
797      * on the action field (button)
798      *
799      * @return disabled reason text
800      * @see #isDisabled()
801      */
802     @BeanTagAttribute(name = "disabledReason")
803     public String getDisabledReason() {
804         return disabledReason;
805     }
806 
807     /**
808      * Setter for the disabled reason text
809      *
810      * @param disabledReason
811      */
812     public void setDisabledReason(String disabledReason) {
813         this.disabledReason = disabledReason;
814     }
815 
816     @BeanTagAttribute(name = "actionImagePlacement")
817     public String getActionImagePlacement() {
818         return actionImagePlacement;
819     }
820 
821     /**
822      * Set to TOP, BOTTOM, LEFT, RIGHT to position image at that location within the button.
823      * For the subclass ActionLinkField only LEFT and RIGHT are allowed.  When set to blank/null/IMAGE_ONLY, the image
824      * itself will be the Action, if no value is set the default is ALWAYS LEFT, you must explicitly set
825      * blank/null/IMAGE_ONLY to use ONLY the image as the Action.
826      *
827      * @return
828      */
829     public void setActionImagePlacement(String actionImagePlacement) {
830         this.actionImagePlacement = actionImagePlacement;
831     }
832 
833     /**
834      * Gets the script which needs to be invoked before the form is submitted
835      *
836      * <p>
837      * The preSubmitCall can carry out custom logic for the action before the submit occurs. The value should
838      * be given as one or more lines of script and should return a boolean. If false is returned from the call,
839      * the submit is not carried out. Furthermore, the preSubmitCall can refer to the request object through the
840      * variable 'kradRequest' or 'this'. This gives full access over the request for doing such things as
841      * adding additional data
842      * </p>
843      *
844      * <p>
845      * Examples 'return doFunction(kradRequest);', 'var valid=true;return valid;'
846      * </p>
847      *
848      * <p>
849      * The preSubmit call will be invoked both for ajax and non-ajax submits
850      * </p>
851      *
852      * @return script text that will be invoked before form submission
853      */
854     @BeanTagAttribute(name = "preSubmitCall")
855     public String getPreSubmitCall() {
856         return preSubmitCall;
857     }
858 
859     /**
860      * Setter for preSubmitCall
861      *
862      * @param preSubmitCall
863      */
864     public void setPreSubmitCall(String preSubmitCall) {
865         this.preSubmitCall = preSubmitCall;
866     }
867 
868     /**
869      * When this property is set to true it will submit the form using Ajax instead of the browser submit. Will default
870      * to updating the page contents
871      *
872      * @return boolean
873      */
874     @BeanTagAttribute(name = "ajaxSubmit")
875     public boolean isAjaxSubmit() {
876         return ajaxSubmit;
877     }
878 
879     /**
880      * Setter for ajaxSubmit
881      *
882      * @param ajaxSubmit
883      */
884     public void setAjaxSubmit(boolean ajaxSubmit) {
885         this.ajaxSubmit = ajaxSubmit;
886     }
887 
888     /**
889      * Gets the return type for the ajax call
890      *
891      * <p>
892      * The ajax return type indicates how the response content will be handled in the client. Typical
893      * examples include updating a component, the page, or doing a redirect.
894      * </p>
895      *
896      * @return return type
897      * @see org.kuali.rice.krad.uif.UifConstants.AjaxReturnTypes
898      */
899     @BeanTagAttribute(name = "ajaxReturnType")
900     public String getAjaxReturnType() {
901         return this.ajaxReturnType;
902     }
903 
904     /**
905      * Setter for the type of ajax return
906      *
907      * @param ajaxReturnType
908      */
909     public void setAjaxReturnType(String ajaxReturnType) {
910         this.ajaxReturnType = ajaxReturnType;
911     }
912 
913     /**
914      * Indicates if the action response should be displayed in a lightbox
915      *
916      * @return true if response should be rendered in a lightbox, false if not
917      */
918     @BeanTagAttribute(name = "displayResponseInLightBox")
919     public boolean isDisplayResponseInLightBox() {
920         return StringUtils.equals(this.ajaxReturnType, UifConstants.AjaxReturnTypes.DISPLAYLIGHTBOX.getKey());
921     }
922 
923     /**
924      * Setter for indicating the response should be rendered in a lightbox
925      *
926      * @param displayResponseInLightBox
927      */
928     public void setDisplayResponseInLightBox(boolean displayResponseInLightBox) {
929         if (displayResponseInLightBox) {
930             this.ajaxReturnType = UifConstants.AjaxReturnTypes.DISPLAYLIGHTBOX.getKey();
931         }
932         // if display lightbox is false and it was previously true, set to default of update page
933         else if (StringUtils.equals(this.ajaxReturnType, UifConstants.AjaxReturnTypes.DISPLAYLIGHTBOX.getKey())) {
934             this.ajaxReturnType = UifConstants.AjaxReturnTypes.UPDATEPAGE.getKey();
935         }
936     }
937 
938     /**
939      * Gets the script which will be invoked on a successful ajax call
940      *
941      * <p>
942      * The successCallback can carry out custom logic after a successful ajax submission has been made. The
943      * value can contain one or more script statements. In addition, the response contents can be accessed
944      * through the variable 'responseContents'
945      * </p>
946      *
947      * <p>
948      * Examples 'handleSuccessfulUpdate(responseContents);'
949      * </p>
950      *
951      * <p>
952      * The successCallback may only be specified when {@link #isAjaxSubmit()} is true
953      * </p>
954      *
955      * @return script to be executed when the action is successful
956      */
957     @BeanTagAttribute(name = "successCallback")
958     public String getSuccessCallback() {
959         return successCallback;
960     }
961 
962     /**
963      * Setter for successCallback
964      *
965      * @param successCallback
966      */
967     public void setSuccessCallback(String successCallback) {
968         this.successCallback = successCallback;
969     }
970 
971     /**
972      * Gets the script which will be invoked when the action fails due to problems in the ajax call or
973      * the return of an incident report
974      *
975      * <p>
976      * The errorCallback can carry out custom logic after a failed ajax submission. The
977      * value can contain one or more script statements. In addition, the response contents can be accessed
978      * through the variable 'responseContents'
979      * </p>
980      *
981      * <p>
982      * Examples 'handleFailedUpdate(responseContents);'
983      * </p>
984      *
985      * <p>
986      * The errorCallback may only be specified when {@link #isAjaxSubmit()} is true
987      * </p>
988      *
989      * @return script to be executed when the action is successful
990      */
991     @BeanTagAttribute(name = "errorCallback")
992     public String getErrorCallback() {
993         return errorCallback;
994     }
995 
996     /**
997      * Setter for errorCallback
998      *
999      * @param errorCallback
1000      */
1001     public void setErrorCallback(String errorCallback) {
1002         this.errorCallback = errorCallback;
1003     }
1004 
1005     /**
1006      * Id for the component that should be refreshed after the action completes
1007      *
1008      * <p>
1009      * Either refresh id or refresh property name can be set to configure the component that should
1010      * be refreshed after the action completes. If both are blank, the page will be refreshed
1011      * </p>
1012      *
1013      * @return valid component id
1014      */
1015     @BeanTagAttribute(name = "refreshId")
1016     public String getRefreshId() {
1017         return refreshId;
1018     }
1019 
1020     /**
1021      * Setter for the component refresh id
1022      *
1023      * @param refreshId
1024      */
1025     public void setRefreshId(String refreshId) {
1026         this.refreshId = refreshId;
1027     }
1028 
1029     /**
1030      * Property name for the {@link org.kuali.rice.krad.uif.field.DataField} that should be refreshed after the action
1031      * completes
1032      *
1033      * <p>
1034      * Either refresh id or refresh property name can be set to configure the component that should
1035      * be refreshed after the action completes. If both are blank, the page will be refreshed
1036      * </p>
1037      *
1038      * <p>
1039      * Property name will be adjusted to use the default binding path unless it contains the form prefix
1040      * </p>
1041      *
1042      * @return valid property name with an associated DataField
1043      * @see org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX
1044      */
1045     @BeanTagAttribute(name = "refreshPropertyName")
1046     public String getRefreshPropertyName() {
1047         return refreshPropertyName;
1048     }
1049 
1050     /**
1051      * Setter for the property name of the DataField that should be refreshed
1052      *
1053      * @param refreshPropertyName
1054      */
1055     public void setRefreshPropertyName(String refreshPropertyName) {
1056         this.refreshPropertyName = refreshPropertyName;
1057     }
1058 
1059     /**
1060      * Gets the loading message used by action's blockUI
1061      *
1062      * @returns String if String is not null, used in place of loading message
1063      */
1064     @BeanTagAttribute(name = "loadingMessageText")
1065     public String getLoadingMessageText() {
1066         return loadingMessageText;
1067     }
1068 
1069     /**
1070      * When this property is set, it is used in place of the loading message text used by the blockUI
1071      *
1072      * @param loadingMessageText
1073      */
1074     public void setLoadingMessageText(String loadingMessageText) {
1075         this.loadingMessageText = loadingMessageText;
1076     }
1077 
1078     /**
1079      * Indicates whether blocking for the action should be disabled
1080      *
1081      * <p>
1082      * By default when an action is invoked part of the page or the entire window is blocked until
1083      * the action completes. If this property is set to true the blocking will not be displayed.
1084      * </p>
1085      *
1086      * <p>
1087      * Currently if an action returns a file download, this property should be set to true. If not, the blocking
1088      * will never get unblocked (because the page does not get notification a file was downloaded)
1089      * </p>
1090      *
1091      * @return true if blocking should be disabled, false if not
1092      */
1093     @BeanTagAttribute(name = "disableBlocking")
1094     public boolean isDisableBlocking() {
1095         return disableBlocking;
1096     }
1097 
1098     /**
1099      * Setter for disabling blocking when the action is invoked
1100      *
1101      * @param disableBlocking
1102      */
1103     public void setDisableBlocking(boolean disableBlocking) {
1104         this.disableBlocking = disableBlocking;
1105     }
1106 
1107     /**
1108      * @see org.kuali.rice.krad.uif.component.Component#completeValidation
1109      */
1110     @Override
1111     public void completeValidation(ValidationTrace tracer) {
1112         tracer.addBean(this);
1113 
1114         // Checks that a label or image ui is presence
1115         if (getActionLabel() == null && getActionImage() == null) {
1116             String currentValues[] = {"actionLabel =" + getActionLabel(), "actionImage =" + getActionImage()};
1117             tracer.createError("ActionLabel and/or actionImage must be set", currentValues);
1118         }
1119 
1120         // Checks that an action is set
1121         if (getJumpToIdAfterSubmit() != null && getJumpToNameAfterSubmit() != null) {
1122             String currentValues[] = {"jumpToIdAfterSubmit =" + getJumpToIdAfterSubmit(),
1123                     "jumpToNameAfterSubmit =" + getJumpToNameAfterSubmit()};
1124             tracer.createWarning("Only 1 jumpTo property should be set", currentValues);
1125         }
1126         super.completeValidation(tracer.getCopy());
1127     }
1128 
1129     /**
1130      * Evaluate the disable condition on controls which disable it on each key up event
1131      *
1132      * @return true if evaluate on key up, false otherwise
1133      */
1134     @BeanTagAttribute(name = "evaluateDisabledOnKeyUp")
1135     public boolean isEvaluateDisabledOnKeyUp() {
1136         return evaluateDisabledOnKeyUp;
1137     }
1138 
1139     /**
1140      * Set evaluateDisableOnKeyUp
1141      *
1142      * @param evaluateDisabledOnKeyUp
1143      */
1144     public void setEvaluateDisabledOnKeyUp(boolean evaluateDisabledOnKeyUp) {
1145         this.evaluateDisabledOnKeyUp = evaluateDisabledOnKeyUp;
1146     }
1147 
1148     /**
1149      * Get the disable condition js derived from the springEL, cannot be set.
1150      *
1151      * @return the disableConditionJs javascript to be evaluated
1152      */
1153     public String getDisabledConditionJs() {
1154         return disabledConditionJs;
1155     }
1156 
1157     /**
1158      * Control names to add handlers to for disable functionality, cannot be set
1159      *
1160      * @return control names to add handlers to for disable
1161      */
1162     public List<String> getDisabledConditionControlNames() {
1163         return disabledConditionControlNames;
1164     }
1165 
1166     /**
1167      * Gets the property names of fields that when changed, will disable this component
1168      *
1169      * @return the property names to monitor for change to disable this component
1170      */
1171     @BeanTagAttribute(name = "disabledWhenChangedPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
1172     public List<String> getDisabledWhenChangedPropertyNames() {
1173         return disabledWhenChangedPropertyNames;
1174     }
1175 
1176     /**
1177      * Sets the property names of fields that when changed, will disable this component
1178      *
1179      * @param disabledWhenChangedPropertyNames
1180      */
1181     public void setDisabledWhenChangedPropertyNames(List<String> disabledWhenChangedPropertyNames) {
1182         this.disabledWhenChangedPropertyNames = disabledWhenChangedPropertyNames;
1183     }
1184 
1185     /**
1186      * Gets the property names of fields that when changed, will enable this component
1187      *
1188      * @return the property names to monitor for change to enable this component
1189      */
1190     @BeanTagAttribute(name = "enabledWhenChangedPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
1191     public List<String> getEnabledWhenChangedPropertyNames() {
1192         return enabledWhenChangedPropertyNames;
1193     }
1194 
1195     /**
1196      * Sets the property names of fields that when changed, will enable this component
1197      *
1198      * @param enabledWhenChangedPropertyNames
1199      */
1200     public void setEnabledWhenChangedPropertyNames(List<String> enabledWhenChangedPropertyNames) {
1201         this.enabledWhenChangedPropertyNames = enabledWhenChangedPropertyNames;
1202     }
1203 }