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 }