001/** 002 * Copyright 2005-2018 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.container; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.util.KeyValue; 021import org.kuali.rice.krad.datadictionary.parse.BeanTag; 022import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 023import org.kuali.rice.krad.datadictionary.parse.BeanTags; 024import org.kuali.rice.krad.uif.UifConstants; 025import org.kuali.rice.krad.uif.UifParameters; 026import org.kuali.rice.krad.uif.UifPropertyPaths; 027import org.kuali.rice.krad.uif.component.Component; 028import org.kuali.rice.krad.uif.element.Action; 029import org.kuali.rice.krad.uif.field.InputField; 030import org.kuali.rice.krad.uif.field.MessageField; 031import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction; 032import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils; 033import org.kuali.rice.krad.uif.util.ComponentFactory; 034import org.kuali.rice.krad.uif.util.LifecycleElement; 035import org.kuali.rice.krad.uif.widget.QuickFinder; 036 037import java.util.ArrayList; 038import java.util.List; 039 040/** 041 * Special type of group that presents the content in a modal dialog. 042 * 043 * <p>A dialog group can be used for many different purposes. First it can be used to give a simple confirmation ( 044 * a prompt with ok/cancel or yes/no options). The {@link org.kuali.rice.krad.uif.element.Action} component contains 045 * properties for adding a confirmation dialog. Next, a dialog can be used to prompt for a response or to gather 046 * addition data on the client. In this situation, the dialog is configured either in the view or external to the view, 047 * and the developers triggers the display of the dialog using the javascript method showDialog. See krad.modal.js 048 * for more information. Dialogs can also be triggered from a controller method (or other piece of server code). Again 049 * the dialog is configured with the view or external to the view, and the controller method triggers the show using 050 * the method {@link org.kuali.rice.krad.web.controller.UifControllerBase#showDialog}.</p> 051 * 052 * <p>A dialog is a group and can be configured like any other general group. For building basic dialogs, there are 053 * convenience properties that can be used. In addition, there are base beans provided with definitions for these 054 * properties. This includes a basic prompt message and responses. Note to have responses with different action 055 * properties, 056 * set the items of the dialog groups footer directly.</p> 057 * 058 * @author Kuali Rice Team (rice.collab@kuali.org) 059 */ 060@BeanTags({@BeanTag(name = "dialog", parent = "Uif-DialogGroup"), 061 @BeanTag(name = "dialogOkCancel", parent = "Uif-DialogGroup-OkCancel"), 062 @BeanTag(name = "dialogOkCancelExpl", parent = "Uif-DialogGroup-OkCancelExpl"), 063 @BeanTag(name = "dialogYesNo", parent = "Uif-DialogGroup-YesNo"), 064 @BeanTag(name = "actionConfirmation", parent = "Uif-ActionConfirmation"), 065 @BeanTag(name = "actionConfirmationExpl", parent = "Uif-ActionConfirmationExpl")}) 066public class DialogGroup extends GroupBase { 067 068 private static final long serialVersionUID = 1L; 069 070 private MessageField prompt; 071 private InputField explanation; 072 073 private List<KeyValue> availableResponses; 074 075 private String dialogCssClass; 076 077 private String onDialogResponseScript; 078 private String onShowDialogScript; 079 private String onHideDialogScript; 080 private String onHiddenDialogScript; 081 082 private boolean destroyDialogOnHidden; 083 084 /** 085 * Default Constructor. 086 */ 087 public DialogGroup() { 088 super(); 089 } 090 091 /** 092 * The following actions are performed in this phase: 093 * 094 * <ul> 095 * <li>If property name nor binding path is set on the explanation field, sets to generic form property</li> 096 * <li>Move custom dialogGroup properties prompt and explanation into items collection if the 097 * items list is not already populated</li> 098 * </ul> 099 * 100 * {@inheritDoc} 101 */ 102 @Override 103 public void performInitialization(Object model) { 104 super.performInitialization(model); 105 106 setRefreshedByAction(true); 107 108 if ((explanation != null) && StringUtils.isBlank(explanation.getPropertyName()) && StringUtils.isBlank( 109 explanation.getBindingInfo().getBindingPath())) { 110 explanation.setPropertyName(UifPropertyPaths.DIALOG_EXPLANATIONS + "['" + getId() + "']"); 111 explanation.getBindingInfo().setBindToForm(true); 112 } 113 114 if ((getItems() == null) || getItems().isEmpty()) { 115 List<Component> items = new ArrayList<Component>(); 116 117 if (prompt != null) { 118 items.add(prompt); 119 } 120 121 if (explanation != null) { 122 items.add(explanation); 123 } 124 125 setItems(items); 126 } 127 } 128 129 /** 130 * The following actions are performed in this phase: 131 * 132 * <ul> 133 * <li>For each configured key value response, create an action component and add to the footer items.</li> 134 * </ul> 135 * 136 * {@inheritDoc} 137 */ 138 @Override 139 public void performApplyModel(Object model, LifecycleElement parent) { 140 super.performApplyModel(model, parent); 141 142 // create action in footer for each configured key value response 143 if ((availableResponses != null) && !availableResponses.isEmpty()) { 144 List<Component> footerItems = new ArrayList<Component>(); 145 146 for (KeyValue keyValue : availableResponses) { 147 Action responseAction = ComponentFactory.getSecondaryAction(); 148 149 responseAction.setDialogDismissOption(UifConstants.DialogDismissOption.PRESUBMIT.name()); 150 responseAction.setDialogResponse(keyValue.getKey()); 151 152 responseAction.setActionLabel(keyValue.getValue()); 153 154 footerItems.add(responseAction); 155 } 156 157 if (getFooter() == null) { 158 setFooter(ComponentFactory.getFooter()); 159 } 160 161 if (getFooter().getItems() != null) { 162 footerItems.addAll(getFooter().getItems()); 163 } 164 165 getFooter().setItems(footerItems); 166 } 167 } 168 169 /** 170 * The following actions are performed in this phase: 171 * 172 * <ul> 173 * <li>Add data attributes for any configured event handlers</li> 174 * </ul> 175 * 176 * {@inheritDoc} 177 */ 178 @Override 179 public void performFinalize(Object model, LifecycleElement parent) { 180 super.performFinalize(model, parent); 181 182 if (StringUtils.isNotBlank(this.onDialogResponseScript)) { 183 addDataAttribute(UifConstants.DataAttributes.DIALOG_RESPONSE_HANDLER, this.onDialogResponseScript); 184 } 185 186 String script = "jQuery.unblockUI();"; 187 if (StringUtils.isNotBlank(this.onShowDialogScript)) { 188 this.onShowDialogScript = script + this.onShowDialogScript; 189 } else { 190 this.onShowDialogScript = script; 191 } 192 addDataAttribute(UifConstants.DataAttributes.DIALOG_SHOW_HANDLER, this.onShowDialogScript); 193 194 if (StringUtils.isNotBlank(this.onHideDialogScript)) { 195 addDataAttribute(UifConstants.DataAttributes.DIALOG_HIDE_HANDLER, this.onHideDialogScript); 196 } 197 198 if (StringUtils.isBlank(this.onHiddenDialogScript)) { 199 this.onHiddenDialogScript = ""; 200 } 201 202 if (destroyDialogOnHidden) { 203 this.onHiddenDialogScript += "destroyDialog('" + getId() + "');"; 204 } 205 206 if (StringUtils.isNotBlank(this.onHiddenDialogScript)) { 207 addDataAttribute(UifConstants.DataAttributes.DIALOG_HIDDEN_HANDLER, this.onHiddenDialogScript); 208 } 209 210 // Dialogs do not have a visual "parent" on the page so remove this data attribute 211 this.getDataAttributes().remove(UifConstants.DataAttributes.PARENT); 212 213 List<QuickFinder> quickFinders = ViewLifecycleUtils.getElementsOfTypeDeep(getItems(), QuickFinder.class); 214 for (QuickFinder quickFinder : quickFinders) { 215 Action quickFinderAction = quickFinder.getQuickfinderAction(); 216 quickFinderAction.addActionParameter(UifParameters.DIALOG_ID, getId()); 217 } 218 } 219 220 /** 221 * Text to be displayed as the prompt or main message in this simple dialog. 222 * 223 * <p>This is a convenience method for setting the message text on {@link DialogGroup#getPrompt()}</p> 224 * 225 * @return String containing the prompt text 226 */ 227 228 @BeanTagAttribute 229 public String getPromptText() { 230 if (prompt != null) { 231 return prompt.getMessage().getMessageText(); 232 } 233 234 return null; 235 } 236 237 /** 238 * @see DialogGroup#getPromptText() 239 */ 240 public void setPromptText(String promptText) { 241 if (prompt == null) { 242 prompt = ComponentFactory.getMessageField(); 243 } 244 245 prompt.setMessageText(promptText); 246 } 247 248 /** 249 * Message component to use for the dialog prompt. 250 * 251 * @return Message component for prompt 252 */ 253 @ViewLifecycleRestriction 254 @BeanTagAttribute 255 public MessageField getPrompt() { 256 return prompt; 257 } 258 259 /** 260 * @see DialogGroup#getPrompt() 261 */ 262 public void setPrompt(MessageField prompt) { 263 this.prompt = prompt; 264 } 265 266 /** 267 * Input field use to gather explanation text with the dialog. 268 * 269 * <p>By default, the control for this input is configured as a TextAreaControl. It may be configured for 270 * other types of input fields.</p> 271 * 272 * @return InputField component 273 */ 274 @ViewLifecycleRestriction 275 @BeanTagAttribute 276 public InputField getExplanation() { 277 return explanation; 278 } 279 280 /** 281 * @see DialogGroup#getExplanation() 282 */ 283 public void setExplanation(InputField explanation) { 284 this.explanation = explanation; 285 } 286 287 /** 288 * List of options that are available for the user to choice as a response to the dialog. 289 * 290 * <p>If given, the list of key value pairs is used to create action components that are inserted into the 291 * dialog footer. The key will be used as the response value, and the value as the label for the action.</p> 292 * 293 * <p>Note responses can be also be created by populating the footer items with action components.</p> 294 * 295 * @return the List of response actions to provide the user 296 */ 297 @BeanTagAttribute 298 public List<KeyValue> getAvailableResponses() { 299 return availableResponses; 300 } 301 302 /** 303 * @see DialogGroup#getAvailableResponses() 304 */ 305 public void setAvailableResponses(List<KeyValue> availableResponses) { 306 this.availableResponses = availableResponses; 307 } 308 309 /** 310 * Gets CSS class to use when rendering dialog (default is modal-sm). 311 * 312 * @return String of CSS class 313 */ 314 @BeanTagAttribute 315 public String getDialogCssClass() { 316 return dialogCssClass; 317 } 318 319 public void setDialogCssClass(String dialogCssClass) { 320 this.dialogCssClass = dialogCssClass; 321 } 322 323 /** 324 * Script that will be invoked when the dialog response event is thrown. 325 * 326 * <p>The dialog group will throw a custom event type 'dialogresponse.uif' when an response action within the 327 * dialog is selected. Script given here will bind to that event as a handler</p> 328 * 329 * <p>The event object contains: 330 * event.response - response value for the action that was selected 331 * event.action - jQuery object for the action element that was selected 332 * event.dialogId - id for the dialog the response applies to</p> 333 * 334 * @return js that will execute for the response event 335 */ 336 @BeanTagAttribute 337 public String getOnDialogResponseScript() { 338 return onDialogResponseScript; 339 } 340 341 /** 342 * @see DialogGroup#getOnDialogResponseScript() 343 */ 344 public void setOnDialogResponseScript(String onDialogResponseScript) { 345 this.onDialogResponseScript = onDialogResponseScript; 346 } 347 348 /** 349 * Script that will get invoked when the dialog group is shown. 350 * 351 * <p>Initially a dialog group will either be hidden in the DOM or not present at all (if retrieved via Ajax). 352 * When the dialog is triggered and shown, a show event will be thrown and this script will 353 * be executed</p> 354 * 355 * @return js code to execute when the dialog is shown 356 */ 357 @BeanTagAttribute 358 public String getOnShowDialogScript() { 359 return onShowDialogScript; 360 } 361 362 /** 363 * @see DialogGroup#getOnShowDialogScript() 364 */ 365 public void setOnShowDialogScript(String onShowDialogScript) { 366 this.onShowDialogScript = onShowDialogScript; 367 } 368 369 /** 370 * Script that will get invoked when the dialog group receives a hide event. 371 * 372 * @return js code to execute when the dialog receives a hide event 373 */ 374 public String getOnHideDialogScript() { 375 return onHideDialogScript; 376 } 377 378 /** 379 * @see DialogGroup#getOnHideDialogScript() 380 */ 381 public void setOnHideDialogScript(String onHideDialogScript) { 382 this.onHideDialogScript = onHideDialogScript; 383 } 384 385 /** 386 * Script that will get invoked once the dialog group is hidden. 387 * 388 * @return js code to execute when the dialog is hidden 389 */ 390 public String getOnHiddenDialogScript() { 391 return onHiddenDialogScript; 392 } 393 394 /** 395 * @see DialogGroup#getOnHiddenDialogScript() 396 */ 397 public void setOnHiddenDialogScript(String onHiddenDialogScript) { 398 this.onHiddenDialogScript = onHiddenDialogScript; 399 } 400 401 /** 402 * Flag to indicate whether the contents of the dialog should be destroyed on hidden. 403 * 404 * @return boolean to destroy contents 405 */ 406 public boolean isDestroyDialogOnHidden() { 407 return destroyDialogOnHidden; 408 } 409 410 /** 411 * @see DialogGroup#isDestroyDialogOnHidden() 412 */ 413 public void setDestroyDialogOnHidden(boolean destroyDialogOnHidden) { 414 this.destroyDialogOnHidden = destroyDialogOnHidden; 415 } 416}