1 /**
2 * Copyright 2005-2016 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 public ActionFieldSecurity getActionFieldSecurity() {
438 return (ActionFieldSecurity) super.getComponentSecurity();
439 }
440
441 /**
442 * Override to assert a {@link ActionFieldSecurity} instance is set
443 *
444 * @param componentSecurity - instance of ActionFieldSecurity
445 */
446 @Override
447 public void setComponentSecurity(ComponentSecurity componentSecurity) {
448 if (!(componentSecurity instanceof ActionFieldSecurity)) {
449 throw new RiceRuntimeException(
450 "Component security for ActionField should be instance of ActionFieldSecurity");
451 }
452
453 super.setComponentSecurity(componentSecurity);
454 }
455
456 @Override
457 protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
458 return ActionFieldSecurity.class;
459 }
460
461 /**
462 * @see org.kuali.rice.krad.uif.component.ComponentBase#getSupportsOnClick()
463 */
464 @Override
465 public boolean getSupportsOnClick() {
466 return true;
467 }
468
469 /**
470 * Setter for the light box lookup widget
471 *
472 * @param lightBoxLookup
473 * <code>LightBoxLookup</code> widget to set
474 */
475 public void setLightBoxLookup(LightBox lightBoxLookup) {
476 this.lightBoxLookup = lightBoxLookup;
477 }
478
479 /**
480 * LightBoxLookup widget for the field
481 * <p>
482 * The light box lookup widget will change the lookup behaviour to open the
483 * lookup in a light box.
484 * </p>
485 *
486 * @return the <code>DirectInquiry</code> field DirectInquiry
487 */
488 public LightBox getLightBoxLookup() {
489 return lightBoxLookup;
490 }
491
492 /**
493 * @return the jumpToIdAfterSubmit
494 */
495 public String getJumpToIdAfterSubmit() {
496 return this.jumpToIdAfterSubmit;
497 }
498
499 /**
500 * The id to jump to in the next page, the element with this id will be
501 * jumped to automatically when the new page is retrieved after a submit.
502 * Using "TOP" or "BOTTOM" will jump to the top or the bottom of the
503 * resulting page. Passing in nothing for both jumpToIdAfterSubmit and
504 * jumpToNameAfterSubmit will result in this ActionField being jumped to by
505 * default if it is present on the new page. WARNING: jumpToIdAfterSubmit
506 * always takes precedence over jumpToNameAfterSubmit, if set.
507 *
508 * @param jumpToIdAfterSubmit
509 * the jumpToIdAfterSubmit to set
510 */
511 public void setJumpToIdAfterSubmit(String jumpToIdAfterSubmit) {
512 this.jumpToIdAfterSubmit = jumpToIdAfterSubmit;
513 }
514
515 /**
516 * The name to jump to in the next page, the element with this name will be
517 * jumped to automatically when the new page is retrieved after a submit.
518 * Passing in nothing for both jumpToIdAfterSubmit and jumpToNameAfterSubmit
519 * will result in this ActionField being jumped to by default if it is
520 * present on the new page. WARNING: jumpToIdAfterSubmit always takes
521 * precedence over jumpToNameAfterSubmit, if set.
522 *
523 * @return the jumpToNameAfterSubmit
524 */
525 public String getJumpToNameAfterSubmit() {
526 return this.jumpToNameAfterSubmit;
527 }
528
529 /**
530 * @param jumpToNameAfterSubmit
531 * the jumpToNameAfterSubmit to set
532 */
533 public void setJumpToNameAfterSubmit(String jumpToNameAfterSubmit) {
534 this.jumpToNameAfterSubmit = jumpToNameAfterSubmit;
535 }
536
537 /**
538 * The id of the field to place focus on in the new page after the new page
539 * is retrieved. Passing in "FIRST" will focus on the first visible input
540 * element on the form. Passing in the empty string will result in this
541 * ActionField being focused.
542 *
543 * @return the focusOnAfterSubmit
544 */
545 public String getFocusOnAfterSubmit() {
546 return this.focusOnAfterSubmit;
547 }
548
549 /**
550 * @param focusOnAfterSubmit
551 * the focusOnAfterSubmit to set
552 */
553 public void setFocusOnAfterSubmit(String focusOnAfterSubmit) {
554 this.focusOnAfterSubmit = focusOnAfterSubmit;
555 }
556
557 /**
558 * Indicates whether the form data should be validated on the client side
559 *
560 * return true if validation should occur, false otherwise
561 */
562 public boolean isClientSideValidate() {
563 return this.clientSideValidate;
564 }
565
566 /**
567 * Setter for the client side validation flag
568 * @param clientSideValidate
569 */
570 public void setClientSideValidate(boolean clientSideValidate) {
571 this.clientSideValidate = clientSideValidate;
572 }
573
574 /**
575 * Client side javascript to be executed when this actionField is clicked.
576 * This overrides the default action for this ActionField so the method
577 * called must explicitly submit, navigate, etc. through js, if necessary.
578 * In addition, this js occurs AFTER onClickScripts set on this field, it
579 * will be the last script executed by the click event. Sidenote: This js is
580 * always called after hidden actionParameters and methodToCall methods are
581 * written by the js to the html form.
582 *
583 * @return the clientSideJs
584 */
585 public String getClientSideJs() {
586 return this.clientSideJs;
587 }
588
589 /**
590 * @param clientSideJs
591 * the clientSideJs to set
592 */
593 public void setClientSideJs(String clientSideJs) {
594 if (!StringUtils.endsWith(clientSideJs, ";")) {
595 clientSideJs = clientSideJs + ";";
596 }
597 this.clientSideJs = clientSideJs;
598 }
599
600 /**
601 * Setter for the light box direct inquiry widget
602 *
603 * @param lightBoxDirectInquiry
604 * <code>LightBox</code> widget to set
605 */
606 public void setLightBoxDirectInquiry(LightBox lightBoxDirectInquiry) {
607 this.lightBoxDirectInquiry = lightBoxDirectInquiry;
608 }
609
610 /**
611 * LightBox widget for the field
612 * <p>
613 * The light box widget will change the direct inquiry behaviour to open up
614 * in a light box.
615 * </p>
616 *
617 * @return the <code>LightBox</code> field LightBox
618 */
619 public LightBox getLightBoxDirectInquiry() {
620 return lightBoxDirectInquiry;
621 }
622
623 /**
624 * @param blockValidateDirty
625 * the blockValidateDirty to set
626 */
627 public void setBlockValidateDirty(boolean blockValidateDirty) {
628 this.blockValidateDirty = blockValidateDirty;
629 }
630
631 /**
632 * @return the blockValidateDirty
633 */
634 public boolean isBlockValidateDirty() {
635 return blockValidateDirty;
636 }
637
638 /**
639 * Indicates whether the action (input or button) is disabled (doesn't allow interaction)
640 *
641 * @return boolean true if the action field is disabled, false if not
642 */
643 public boolean isDisabled() {
644 return disabled;
645 }
646
647 /**
648 * If the action field is disabled, gives a reason for why which will be displayed as a tooltip
649 * on the action field (button)
650 *
651 * @return String disabled reason text
652 * @see {@link #isDisabled()}
653 */
654 public String getDisabledReason() {
655 return disabledReason;
656 }
657
658 /**
659 * Setter for the disabled reason text
660 *
661 * @param disabledReason
662 */
663 public void setDisabledReason(String disabledReason) {
664 this.disabledReason = disabledReason;
665 }
666
667 /**
668 * Setter for the disabled indicator
669 *
670 * @param disabled
671 */
672 public void setDisabled(boolean disabled) {
673 this.disabled = disabled;
674 }
675
676 public String getActionImageLocation() {
677 return actionImageLocation;
678 }
679
680 /**
681 * Set to TOP, BOTTOM, LEFT, RIGHT to position image at that location within the button.
682 * For the subclass ActionLinkField only LEFT and RIGHT are allowed. When set to blank/null/IMAGE_ONLY, the image
683 * itself will be the ActionField, if no value is set the default is ALWAYS LEFT, you must explicitly set
684 * blank/null/IMAGE_ONLY to use ONLY the image as the ActionField.
685 * @return
686 */
687 public void setActionImageLocation(String actionImageLocation) {
688 this.actionImageLocation = actionImageLocation;
689 }
690 }