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