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 }