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