001/** 002 * Copyright 2005-2016 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.field; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.exception.RiceRuntimeException; 020import org.kuali.rice.krad.uif.UifConstants; 021import org.kuali.rice.krad.uif.UifParameters; 022import org.kuali.rice.krad.uif.UifPropertyPaths; 023import org.kuali.rice.krad.uif.component.ComponentSecurity; 024import org.kuali.rice.krad.uif.view.FormView; 025import org.kuali.rice.krad.uif.view.View; 026import org.kuali.rice.krad.uif.component.Component; 027import org.kuali.rice.krad.uif.widget.LightBox; 028 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032 033/** 034 * Field that presents an action that can be taken on the UI such as submitting 035 * the form or invoking a script 036 * 037 * @author Kuali Rice Team (rice.collab@kuali.org) 038 */ 039public class ActionField extends FieldBase { 040 private static final long serialVersionUID = 1025672792657238829L; 041 042 private String methodToCall; 043 private String navigateToPageId; 044 045 private boolean clientSideValidate; 046 private String clientSideJs; 047 048 private String jumpToIdAfterSubmit; 049 private String jumpToNameAfterSubmit; 050 private String focusOnAfterSubmit; 051 052 private String actionLabel; 053 private ImageField actionImage; 054 private String actionImageLocation = "LEFT"; 055 056 private String actionEvent; 057 private Map<String, String> actionParameters; 058 059 private LightBox lightBoxLookup; 060 private LightBox lightBoxDirectInquiry; 061 062 private boolean blockValidateDirty; 063 private boolean disabled; 064 private String disabledReason; 065 066 public ActionField() { 067 super(); 068 069 disabled = false; 070 071 actionParameters = new HashMap<String, String>(); 072 } 073 074 /** 075 * The following initialization is performed: 076 * 077 * <ul> 078 * <li>Set the actionLabel if blank to the Field label</li> 079 * </ul> 080 * 081 * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View, java.lang.Object) 082 */ 083 @Override 084 public void performInitialization(View view, Object model) { 085 super.performInitialization(view, model); 086 087 if (StringUtils.isBlank(actionLabel)) { 088 actionLabel = this.getLabel(); 089 } 090 } 091 092 /** 093 * The following finalization is performed: 094 * 095 * <ul> 096 * <li>Add methodToCall action parameter if set and setup event code for 097 * setting action parameters</li> 098 * </ul> 099 * 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}