View Javadoc

1   /**
2    * Copyright 2005-2012 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.field;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.exception.RiceRuntimeException;
20  import org.kuali.rice.krad.uif.UifConstants;
21  import org.kuali.rice.krad.uif.UifParameters;
22  import org.kuali.rice.krad.uif.UifPropertyPaths;
23  import org.kuali.rice.krad.uif.component.ComponentSecurity;
24  import org.kuali.rice.krad.uif.view.FormView;
25  import org.kuali.rice.krad.uif.view.View;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.widget.LightBox;
28  
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  /**
34   * Field that presents an action that can be taken on the UI such as submitting
35   * the form or invoking a script
36   * 
37   * @author Kuali Rice Team (rice.collab@kuali.org)
38   */
39  public class ActionField extends FieldBase {
40      private static final long serialVersionUID = 1025672792657238829L;
41  
42      private String methodToCall;
43      private String navigateToPageId;
44  
45      private boolean clientSideValidate;
46      private String clientSideJs;
47  
48      private String jumpToIdAfterSubmit;
49      private String jumpToNameAfterSubmit;
50      private String focusOnAfterSubmit;
51  
52      private String actionLabel;
53      private ImageField actionImage;
54      private String actionImageLocation = "LEFT";
55  
56      private String actionEvent;
57      private Map<String, String> actionParameters;
58  
59      private LightBox lightBoxLookup;
60      private LightBox lightBoxDirectInquiry;
61  
62      private boolean blockValidateDirty;
63      private boolean disabled;
64      private String disabledReason;
65  
66      public ActionField() {
67          super();
68  
69          disabled = false;
70  
71          actionParameters = new HashMap<String, String>();
72      }
73  
74      /**
75       * The following initialization is performed:
76       *
77       * <ul>
78       * <li>Set the actionLabel if blank to the Field label</li>
79       * </ul>
80       * 
81       * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View, java.lang.Object)
82       */
83      @Override
84      public void performInitialization(View view, Object model) {
85          super.performInitialization(view, model);
86  
87          if (StringUtils.isBlank(actionLabel)) {
88              actionLabel = this.getLabel();
89          }
90      }
91  
92      /**
93       * The following finalization is performed:
94       *
95       * <ul>
96       * <li>Add methodToCall action parameter if set and setup event code for
97       * setting action parameters</li>
98       * </ul>
99       * 
100      * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
101      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
102      */
103     @Override
104     public void performFinalize(View view, Object model, Component parent) {
105         super.performFinalize(view, model, parent);
106         //clear alt text to avoid screen reader confusion when using image in button with text
107         if(actionImage != null && StringUtils.isNotBlank(actionImageLocation) && StringUtils.isNotBlank(actionLabel)){
108             actionImage.setAltText("");
109         }
110 
111         if (!actionParameters.containsKey(UifConstants.UrlParams.ACTION_EVENT) && StringUtils.isNotBlank(actionEvent)) {
112             actionParameters.put(UifConstants.UrlParams.ACTION_EVENT, actionEvent);
113         }
114 
115         actionParameters.put(UifConstants.UrlParams.SHOW_HOME, "false");
116         actionParameters.put(UifConstants.UrlParams.SHOW_HISTORY, "false");
117 
118         if (StringUtils.isNotBlank(navigateToPageId)) {
119             actionParameters.put(UifParameters.NAVIGATE_TO_PAGE_ID, navigateToPageId);
120             if (StringUtils.isBlank(methodToCall)) {
121                 actionParameters.put(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME,
122                         UifConstants.MethodToCallNames.NAVIGATE);
123             }
124         }
125 
126         if (!actionParameters.containsKey(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME)
127                 && StringUtils.isNotBlank(methodToCall)) {
128             actionParameters.put(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME, methodToCall);
129         }
130 
131         // If there is no lightBox then create the on click script
132         if (lightBoxLookup == null) {
133             String prefixScript = this.getOnClickScript();
134             if (prefixScript == null) {
135                 prefixScript = "";
136             }
137 
138             boolean validateFormDirty = false;
139             if (view instanceof FormView && !isBlockValidateDirty()) {
140                 validateFormDirty = ((FormView) view).isValidateDirty();
141             }
142 
143             boolean includeDirtyCheckScript = false;
144             String writeParamsScript = "";
145             if (!actionParameters.isEmpty()) {
146                 for (String key : actionParameters.keySet()) {
147                     String parameterPath = key;
148                     if (!key.equals(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME)) {
149                         parameterPath = UifPropertyPaths.ACTION_PARAMETERS + "[" + key + "]";
150                     }
151 
152                     writeParamsScript = writeParamsScript + "writeHiddenToForm('" + parameterPath + "' , '"
153                             + actionParameters.get(key) + "'); ";
154 
155                     // Include dirtycheck js function call if the method to call
156                     // is refresh, navigate, cancel or close
157                     if (validateFormDirty && !includeDirtyCheckScript
158                             && key.equals(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME)) {
159                         String keyValue = (String) actionParameters.get(key);
160                         if (StringUtils.equals(keyValue, UifConstants.MethodToCallNames.REFRESH)
161                                 || StringUtils.equals(keyValue, UifConstants.MethodToCallNames.NAVIGATE)
162                                 || StringUtils.equals(keyValue, UifConstants.MethodToCallNames.CANCEL)
163                                 || StringUtils.equals(keyValue, UifConstants.MethodToCallNames.CLOSE)) {
164                             includeDirtyCheckScript = true;
165                         }
166                     }
167                 }
168             }
169 
170             // TODO possibly fix some other way - this is a workaround, prevents
171             // showing history and showing home again on actions which submit
172             // the form
173             writeParamsScript = writeParamsScript + "writeHiddenToForm('" + UifConstants.UrlParams.SHOW_HISTORY
174                     + "', '" + "false" + "'); ";
175             writeParamsScript = writeParamsScript + "writeHiddenToForm('" + UifConstants.UrlParams.SHOW_HOME + "' , '"
176                     + "false" + "'); ";
177 
178             if (StringUtils.isBlank(focusOnAfterSubmit)) {
179                 // if this is blank focus this actionField by default
180                 focusOnAfterSubmit = this.getId();
181                 writeParamsScript = writeParamsScript + "writeHiddenToForm('focusId' , '" + this.getId() + "'); ";
182             } else if (!focusOnAfterSubmit.equalsIgnoreCase(UifConstants.Order.FIRST.toString())) {
183                 // Use the id passed in
184                 writeParamsScript = writeParamsScript + "writeHiddenToForm('focusId' , '" + focusOnAfterSubmit + "'); ";
185             } else {
186                 // First input will be focused, must be first field set to empty
187                 // string
188                 writeParamsScript = writeParamsScript + "writeHiddenToForm('focusId' , ''); ";
189             }
190 
191             if (StringUtils.isBlank(jumpToIdAfterSubmit) && StringUtils.isBlank(jumpToNameAfterSubmit)) {
192                 jumpToIdAfterSubmit = this.getId();
193                 writeParamsScript = writeParamsScript + "writeHiddenToForm('jumpToId' , '" + this.getId() + "'); ";
194             } else if (StringUtils.isNotBlank(jumpToIdAfterSubmit)) {
195                 writeParamsScript = writeParamsScript + "writeHiddenToForm('jumpToId' , '" + jumpToIdAfterSubmit
196                         + "'); ";
197             } else {
198                 writeParamsScript = writeParamsScript + "writeHiddenToForm('jumpToName' , '" + jumpToNameAfterSubmit
199                         + "'); ";
200             }
201             
202             String postScript = "";
203             if (StringUtils.isNotBlank(clientSideJs)) {
204                 postScript = clientSideJs;
205             }
206             if (isClientSideValidate()) {
207                 postScript = postScript + "validateAndSubmitUsingFormMethodToCall();";
208             }
209             if (StringUtils.isBlank(postScript)) {
210                 postScript = "writeHiddenToForm('renderFullView' , 'true'); jq('#kualiForm').submit();";
211             }
212 
213             if (includeDirtyCheckScript) {
214                 this.setOnClickScript("e.preventDefault(); if (checkDirty(e) == false) { " + prefixScript
215                         + writeParamsScript + postScript + " ; } ");
216             } else {
217                 this.setOnClickScript("e.preventDefault();" + prefixScript + writeParamsScript + postScript);
218             }
219 
220         } else {
221             // When there is a light box - don't add the on click script as it
222             // will be prevented from executing
223             // Create a script map object which will be written to the form on
224             // click event
225             StringBuffer sb = new StringBuffer();
226             sb.append("{");
227             for (String key : actionParameters.keySet()) {
228                 String optionValue = actionParameters.get(key);
229                 if (sb.length() > 1) {
230                     sb.append(",");
231                 }
232                 if (!key.equals(UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME)) {
233                     sb.append("\"" + UifPropertyPaths.ACTION_PARAMETERS + "[" + key + "]" + "\"");
234                 } else {
235                     sb.append("\"" + key + "\"");
236                 }
237                 sb.append(":");
238                 sb.append("\"" + optionValue + "\"");
239             }
240             sb.append("}");
241             lightBoxLookup.setActionParameterMapString(sb.toString());
242         }
243     }
244 
245     /**
246      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
247      */
248     @Override
249     public List<Component> getComponentsForLifecycle() {
250         List<Component> components = super.getComponentsForLifecycle();
251 
252         components.add(actionImage);
253         components.add(lightBoxLookup);
254         components.add(lightBoxDirectInquiry);
255 
256         return components;
257     }
258 
259     /**
260      * Name of the method that should be called when the action is selected
261      * <p>
262      * For a server side call (clientSideCall is false), gives the name of the
263      * method in the mapped controller that should be invoked when the action is
264      * selected. For client side calls gives the name of the script function
265      * that should be invoked when the action is selected
266      * </p>
267      * 
268      * @return String name of method to call
269      */
270     public String getMethodToCall() {
271         return this.methodToCall;
272     }
273 
274     /**
275      * Setter for the actions method to call
276      * 
277      * @param methodToCall
278      */
279     public void setMethodToCall(String methodToCall) {
280         this.methodToCall = methodToCall;
281     }
282 
283     /**
284      * Label text for the action
285      * <p>
286      * The label text is used by the template renderers to give a human readable
287      * label for the action. For buttons this generally is the button text,
288      * while for an action link it would be the links displayed text
289      * </p>
290      * 
291      * @return String label for action
292      */
293     public String getActionLabel() {
294         return this.actionLabel;
295     }
296 
297     /**
298      * Setter for the actions label
299      * 
300      * @param actionLabel
301      */
302     public void setActionLabel(String actionLabel) {
303         this.actionLabel = actionLabel;
304     }
305 
306     /**
307      * Image to use for the action
308      * <p>
309      * When the action image field is set (and render is true) the image will be
310      * used to present the action as opposed to the default (input submit). For
311      * action link templates the image is used for the link instead of the
312      * action link text
313      * </p>
314      * 
315      * @return ImageField action image
316      */
317     public ImageField getActionImage() {
318         return this.actionImage;
319     }
320 
321     /**
322      * Setter for the action image field
323      * 
324      * @param actionImage
325      */
326     public void setActionImage(ImageField actionImage) {
327         this.actionImage = actionImage;
328     }
329 
330     /**
331      * For an <code>ActionField</code> that is part of a
332      * <code>NavigationGroup</code, the navigate to page id can be set to
333      * configure the page that should be navigated to when the action is
334      * selected
335      * <p>
336      * Support exists in the <code>UifControllerBase</code> for handling
337      * navigation between pages
338      * </p>
339      * 
340      * @return String id of page that should be rendered when the action item is
341      *         selected
342      */
343     public String getNavigateToPageId() {
344         return this.navigateToPageId;
345     }
346 
347     /**
348      * Setter for the navigate to page id
349      * 
350      * @param navigateToPageId
351      */
352     public void setNavigateToPageId(String navigateToPageId) {
353         this.navigateToPageId = navigateToPageId;
354         actionParameters.put(UifParameters.NAVIGATE_TO_PAGE_ID, navigateToPageId);
355         this.methodToCall = UifConstants.MethodToCallNames.NAVIGATE;
356     }
357 
358     /**
359      * Name of the event that will be set when the action is invoked
360      *
361      * <p>
362      * Action events can be looked at by the view or components in order to render differently depending on
363      * the action requested.
364      * </p>
365      *
366      * @return String action event name
367      * @see org.kuali.rice.krad.uif.UifConstants.ActionEvents
368      */
369     public String getActionEvent() {
370         return actionEvent;
371     }
372 
373     /**
374      * Setter for the action event
375      *
376      * @param actionEvent
377      */
378     public void setActionEvent(String actionEvent) {
379         this.actionEvent = actionEvent;
380     }
381 
382     /**
383      * Parameters that should be sent when the action is invoked
384      * <p>
385      * Action renderer will decide how the parameters are sent for the action
386      * (via script generated hiddens, or script parameters, ...)
387      * </p>
388      * <p>
389      * Can be set by other components such as the <code>CollectionGroup</code>
390      * to provide the context the action is in (such as the collection name and
391      * line the action applies to)
392      * </p>
393      * 
394      * @return Map<String, String> action parameters
395      */
396     public Map<String, String> getActionParameters() {
397         return this.actionParameters;
398     }
399 
400     /**
401      * Setter for the action parameters
402      * 
403      * @param actionParameters
404      */
405     public void setActionParameters(Map<String, String> actionParameters) {
406         this.actionParameters = actionParameters;
407     }
408 
409     /**
410      * Convenience method to add a parameter to the action parameters Map
411      * 
412      * @param parameterName
413      *            - name of parameter to add
414      * @param parameterValue
415      *            - value of parameter to add
416      */
417     public void addActionParameter(String parameterName, String parameterValue) {
418         if (actionParameters == null) {
419             this.actionParameters = new HashMap<String, String>();
420         }
421 
422         this.actionParameters.put(parameterName, parameterValue);
423     }
424 
425     /**
426      * Get an actionParameter by name
427      */
428     public String getActionParameter(String parameterName) {
429         return this.actionParameters.get(parameterName);
430     }
431 
432     /**
433      * Action Field Security object that indicates what authorization (permissions) exist for the action
434      *
435      * @return ActionFieldSecurity instance
436      */
437     @Override
438     public ActionFieldSecurity getComponentSecurity() {
439         return (ActionFieldSecurity) super.getComponentSecurity();
440     }
441 
442     /**
443      * Override to assert a {@link ActionFieldSecurity} instance is set
444      *
445      * @param componentSecurity - instance of ActionFieldSecurity
446      */
447     @Override
448     public void setComponentSecurity(ComponentSecurity componentSecurity) {
449         if (!(componentSecurity instanceof ActionFieldSecurity)) {
450             throw new RiceRuntimeException(
451                     "Component security for ActionField should be instance of ActionFieldSecurity");
452         }
453 
454         super.setComponentSecurity(componentSecurity);
455     }
456 
457     @Override
458     protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
459         return ActionFieldSecurity.class;
460     }
461 
462     /**
463      * @see org.kuali.rice.krad.uif.component.ComponentBase#getSupportsOnClick()
464      */
465     @Override
466     public boolean getSupportsOnClick() {
467         return true;
468     }
469 
470     /**
471      * Setter for the light box lookup widget
472      * 
473      * @param lightBoxLookup
474      *            <code>LightBoxLookup</code> widget to set
475      */
476     public void setLightBoxLookup(LightBox lightBoxLookup) {
477         this.lightBoxLookup = lightBoxLookup;
478     }
479 
480     /**
481      * LightBoxLookup widget for the field
482      * <p>
483      * The light box lookup widget will change the lookup behaviour to open the
484      * lookup in a light box.
485      * </p>
486      * 
487      * @return the <code>DirectInquiry</code> field DirectInquiry
488      */
489     public LightBox getLightBoxLookup() {
490         return lightBoxLookup;
491     }
492 
493     /**
494      * @return the jumpToIdAfterSubmit
495      */
496     public String getJumpToIdAfterSubmit() {
497         return this.jumpToIdAfterSubmit;
498     }
499 
500     /**
501      * The id to jump to in the next page, the element with this id will be
502      * jumped to automatically when the new page is retrieved after a submit.
503      * Using "TOP" or "BOTTOM" will jump to the top or the bottom of the
504      * resulting page. Passing in nothing for both jumpToIdAfterSubmit and
505      * jumpToNameAfterSubmit will result in this ActionField being jumped to by
506      * default if it is present on the new page. WARNING: jumpToIdAfterSubmit
507      * always takes precedence over jumpToNameAfterSubmit, if set.
508      * 
509      * @param jumpToIdAfterSubmit
510      *            the jumpToIdAfterSubmit to set
511      */
512     public void setJumpToIdAfterSubmit(String jumpToIdAfterSubmit) {
513         this.jumpToIdAfterSubmit = jumpToIdAfterSubmit;
514     }
515 
516     /**
517      * The name to jump to in the next page, the element with this name will be
518      * jumped to automatically when the new page is retrieved after a submit.
519      * Passing in nothing for both jumpToIdAfterSubmit and jumpToNameAfterSubmit
520      * will result in this ActionField being jumped to by default if it is
521      * present on the new page. WARNING: jumpToIdAfterSubmit always takes
522      * precedence over jumpToNameAfterSubmit, if set.
523      * 
524      * @return the jumpToNameAfterSubmit
525      */
526     public String getJumpToNameAfterSubmit() {
527         return this.jumpToNameAfterSubmit;
528     }
529 
530     /**
531      * @param jumpToNameAfterSubmit
532      *            the jumpToNameAfterSubmit to set
533      */
534     public void setJumpToNameAfterSubmit(String jumpToNameAfterSubmit) {
535         this.jumpToNameAfterSubmit = jumpToNameAfterSubmit;
536     }
537 
538     /**
539      * The id of the field to place focus on in the new page after the new page
540      * is retrieved. Passing in "FIRST" will focus on the first visible input
541      * element on the form. Passing in the empty string will result in this
542      * ActionField being focused.
543      * 
544      * @return the focusOnAfterSubmit
545      */
546     public String getFocusOnAfterSubmit() {
547         return this.focusOnAfterSubmit;
548     }
549 
550     /**
551      * @param focusOnAfterSubmit
552      *            the focusOnAfterSubmit to set
553      */
554     public void setFocusOnAfterSubmit(String focusOnAfterSubmit) {
555         this.focusOnAfterSubmit = focusOnAfterSubmit;
556     }
557 
558     /**
559      * Indicates whether the form data should be validated on the client side
560      *
561      * return true if validation should occur, false otherwise
562      */
563     public boolean isClientSideValidate() {
564         return this.clientSideValidate;
565     }
566 
567     /**
568      * Setter for the client side validation flag
569      * @param clientSideValidate
570      */
571     public void setClientSideValidate(boolean clientSideValidate) {
572         this.clientSideValidate = clientSideValidate;
573     }
574 
575     /**
576      * Client side javascript to be executed when this actionField is clicked.
577      * This overrides the default action for this ActionField so the method
578      * called must explicitly submit, navigate, etc. through js, if necessary.
579      * In addition, this js occurs AFTER onClickScripts set on this field, it
580      * will be the last script executed by the click event. Sidenote: This js is
581      * always called after hidden actionParameters and methodToCall methods are
582      * written by the js to the html form.
583      * 
584      * @return the clientSideJs
585      */
586     public String getClientSideJs() {
587         return this.clientSideJs;
588     }
589 
590     /**
591      * @param clientSideJs
592      *            the clientSideJs to set
593      */
594     public void setClientSideJs(String clientSideJs) {
595         if (!StringUtils.endsWith(clientSideJs, ";")) {
596             clientSideJs = clientSideJs + ";";
597         }
598         this.clientSideJs = clientSideJs;
599     }
600 
601     /**
602      * Setter for the light box direct inquiry widget
603      * 
604      * @param lightBoxDirectInquiry
605      *            <code>LightBox</code> widget to set
606      */
607     public void setLightBoxDirectInquiry(LightBox lightBoxDirectInquiry) {
608         this.lightBoxDirectInquiry = lightBoxDirectInquiry;
609     }
610 
611     /**
612      * LightBox widget for the field
613      * <p>
614      * The light box widget will change the direct inquiry behaviour to open up
615      * in a light box.
616      * </p>
617      * 
618      * @return the <code>LightBox</code> field LightBox
619      */
620     public LightBox getLightBoxDirectInquiry() {
621         return lightBoxDirectInquiry;
622     }
623 
624     /**
625      * @param blockValidateDirty
626      *            the blockValidateDirty to set
627      */
628     public void setBlockValidateDirty(boolean blockValidateDirty) {
629         this.blockValidateDirty = blockValidateDirty;
630     }
631 
632     /**
633      * @return the blockValidateDirty
634      */
635     public boolean isBlockValidateDirty() {
636         return blockValidateDirty;
637     }
638 
639     /**
640      * Indicates whether the action (input or button) is disabled (doesn't allow interaction)
641      *
642      * @return boolean true if the action field is disabled, false if not
643      */
644     public boolean isDisabled() {
645         return disabled;
646     }
647 
648     /**
649      * If the action field is disabled, gives a reason for why which will be displayed as a tooltip
650      * on the action field (button)
651      *
652      * @return String disabled reason text
653      * @see {@link #isDisabled()}
654      */
655     public String getDisabledReason() {
656         return disabledReason;
657     }
658 
659     /**
660      * Setter for the disabled reason text
661      *
662      * @param disabledReason
663      */
664     public void setDisabledReason(String disabledReason) {
665         this.disabledReason = disabledReason;
666     }
667 
668     /**
669      * Setter for the disabled indicator
670      *
671      * @param disabled
672      */
673     public void setDisabled(boolean disabled) {
674         this.disabled = disabled;
675     }
676 
677     public String getActionImageLocation() {
678         return actionImageLocation;
679     }
680 
681     /**
682      * Set to TOP, BOTTOM, LEFT, RIGHT to position image at that location within the button.
683      * For the subclass ActionLinkField only LEFT and RIGHT are allowed.  When set to blank/null/IMAGE_ONLY, the image
684      * itself will be the ActionField, if no value is set the default is ALWAYS LEFT, you must explicitly set
685      * blank/null/IMAGE_ONLY to use ONLY the image as the ActionField.
686      * @return
687      */
688     public void setActionImageLocation(String actionImageLocation) {
689         this.actionImageLocation = actionImageLocation;
690     }
691 }