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.container; 017 018import org.kuali.rice.core.api.util.KeyValue; 019import org.kuali.rice.krad.datadictionary.parse.BeanTag; 020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 021import org.kuali.rice.krad.datadictionary.parse.BeanTags; 022import org.kuali.rice.krad.uif.UifConstants; 023import org.kuali.rice.krad.uif.component.Component; 024import org.kuali.rice.krad.uif.control.MultiValueControl; 025import org.kuali.rice.krad.uif.field.InputField; 026import org.kuali.rice.krad.uif.field.MessageField; 027import org.kuali.rice.krad.uif.util.ScriptUtils; 028import org.kuali.rice.krad.uif.view.View; 029 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.List; 033 034/** 035 * Special type of <code>Group</code> that presents a the content for a modal dialog 036 * 037 * <p> 038 * This type of group will be hidden when the main view is displayed. It will be used as 039 * content inside the LightBox widget when the modal dialog is displayed. 040 * For convenience, this group contains a standard set of components for commonly used modal dialogs 041 * <ul> 042 * <li>a prompt to display in the lightbox</li> 043 * <li>an optional explanation <code>InputField</code> for holding the user's textual response</li> 044 * <li>a set of response options for the user to choose from</li> 045 * </ul> 046 * 047 * <p> 048 * The DialogGroup may also serve as a base class for more complex dialogs. 049 * The default settings for this DialogGroup is to display a prompt message 050 * with two buttons labeled OK and Cancel. 051 * The optional explanation <code>TextAreaControl</code> is hidden by default. 052 * </p> 053 * 054 * <p> 055 * The prompt text, number of user options and their corresponding label are configurable. 056 * The <code>InputField</code> for the explanation is <code>TextAreaControl</code> by default. 057 * It may be configured to other types of InputFields. 058 * The Component for ResponseInputField is a <code>HorizontalCheckboxGroup</code> by default. 059 * JQuery styling is then used to style the checkboxes as buttons. The ResponseInputField may 060 * be configured to other <code>InputField</code> types. 061 * </p> 062 * 063 * @author Kuali Rice Team (rice.collab@kuali.org) 064 */ 065@BeanTags({@BeanTag(name = "dialogGroup-bean", parent = "Uif-DialogGroup"), 066 @BeanTag(name = "sensitiveData-dialogGroup-bean", parent = "Uif-SensitiveData-DialogGroup"), 067 @BeanTag(name = "ok-cancel-dialogGroup-bean", parent = "Uif-OK-Cancel-DialogGroup"), 068 @BeanTag(name = "yes-no-dialogGroup-bean", parent = "Uif-Yes-No-DialogGroup"), 069 @BeanTag(name = "true-false-dialogGroup-bean", parent = "Uif-True-False-DialogGroup"), 070 @BeanTag(name = "checkbox-dialogGroup-bean", parent = "Uif-Checkbox-DialogGroup"), 071 @BeanTag(name = "radioButton-dialogGroup-bean", parent = "Uif-RadioButton-DialogGroup")}) 072public class DialogGroup extends Group { 073 private static final long serialVersionUID = 1L; 074 075 private String promptText; 076 private List<KeyValue> availableResponses; 077 078 private MessageField prompt; 079 private InputField explanation; 080 private InputField responseInputField; 081 082 private boolean reverseButtonOrder; 083 private boolean displayExplanation; 084 085 private String onDialogResponseScript; 086 private String onShowDialogScript; 087 088 public DialogGroup() { 089 super(); 090 } 091 092 /** 093 * The following actions are performed in this phase: 094 * 095 * <ul> 096 * <li>Move custom dialogGroup properties prompt, explanation, and responseInputField into items collection if they 097 * are not already present</li> 098 * </ul> 099 * 100 * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View, 101 * java.lang.Object) 102 */ 103 @Override 104 public void performInitialization(View view, Object model) { 105 super.performInitialization(view, model); 106 107 // move dialogGroup custom properties into the items property. 108 // where they will be rendered by group.jsp 109 List<Component> newItems = new ArrayList<Component>(); 110 List<? extends Component> items = getItems(); 111 112 // do not add the custom properties if they are already present 113 if (!(items.contains(prompt))) { 114 view.assignComponentIds(prompt); 115 newItems.add(prompt); 116 } 117 118 if (!(items.contains(explanation))) { 119 view.assignComponentIds(explanation); 120 newItems.add(explanation); 121 } 122 123 newItems.addAll(getItems()); 124 125 if (!(items.contains(responseInputField))) { 126 view.assignComponentIds(responseInputField); 127 newItems.add(responseInputField); 128 } 129 130 this.setItems(newItems); 131 } 132 133 /** 134 * The following actions are performed in this phase: 135 * 136 * <p> 137 * <ul> 138 * <li>set the promptText in the message</li> 139 * <li>sets whether to render explanation field</li> 140 * <li>set the options for the checkbox control to the availableResponses KeyValue property of 141 * this dialogGroup</li> 142 * <li>orders response buttons</li> 143 * </ul> 144 * </p> 145 * 146 * @see org.kuali.rice.krad.uif.component.ComponentBase#performApplyModel(org.kuali.rice.krad.uif.view.View, 147 * java.lang.Object, org.kuali.rice.krad.uif.component.Component) 148 */ 149 @Override 150 public void performApplyModel(View view, Object model, Component parent) { 151 super.performApplyModel(view, model, parent); 152 153 // set the messageTest to the promptText 154 prompt.setMessageText(promptText); 155 156 // hide or show explanation 157 explanation.setRender(displayExplanation); 158 159 // add options to checkbox 160 if (responseInputField.getControl() != null && responseInputField.getControl() instanceof MultiValueControl) { 161 MultiValueControl multiValueControl = (MultiValueControl) responseInputField.getControl(); 162 163 if (reverseButtonOrder) { 164 // reverse the button order (without changing original list) 165 List<KeyValue> buttonList = new ArrayList<KeyValue>(availableResponses); 166 Collections.reverse(buttonList); 167 multiValueControl.setOptions(buttonList); 168 } else { 169 multiValueControl.setOptions(availableResponses); 170 } 171 } 172 } 173 174 /** 175 * The following actions are performed in this phase: 176 * 177 * <p> 178 * <ul> 179 * <li>handle render via ajax configuration</li> 180 * <li>adds script to the response input field for trigger the 'response' event</li> 181 * </ul> 182 * </p> 183 * 184 * @param view view instance that should be finalized for rendering 185 * @param model top level object containing the data 186 * @param parent parent component 187 */ 188 @Override 189 public void performFinalize(View view, Object model, Component parent) { 190 super.performFinalize(view, model, parent); 191 192 if (responseInputField != null) { 193 String responseInputSelector = "#" + responseInputField.getId() + " [name='" + 194 responseInputField.getBindingInfo().getBindingPath() + "']"; 195 196 String onChangeScript = "var value = coerceValue(\"" + responseInputField.getBindingInfo().getBindingPath() 197 + "\");"; 198 onChangeScript += "jQuery('#" + getId() + "').trigger({type:'" + UifConstants.JsEvents.DIALOG_RESPONSE 199 + "',value:value});"; 200 201 String onChangeHandler = "jQuery(\"" + responseInputSelector + "\").change(function(e){" + onChangeScript 202 + "});"; 203 204 String onReadyScript = ScriptUtils.appendScript(getOnDocumentReadyScript(), onChangeHandler); 205 setOnDocumentReadyScript(onReadyScript); 206 } 207 } 208 209 /** 210 * Override to add the handler script for the dialog response and show dialog events 211 * 212 * @see org.kuali.rice.krad.uif.component.Component#getEventHandlerScript() 213 */ 214 @Override 215 public String getEventHandlerScript() { 216 String handlerScript = super.getEventHandlerScript(); 217 218 handlerScript += ScriptUtils.buildEventHandlerScript(getId(), UifConstants.JsEvents.DIALOG_RESPONSE, 219 getOnDialogResponseScript()); 220 221 handlerScript += ScriptUtils.buildEventHandlerScript(getId(), UifConstants.JsEvents.SHOW_DIALOG, 222 getOnShowDialogScript()); 223 224 return handlerScript; 225 } 226 227 /** 228 * Returns the text to be displayed as the prompt or main message in this simple dialog 229 * 230 * @return String containing the prompt text 231 */ 232 233 @BeanTagAttribute(name = "promptText") 234 public String getPromptText() { 235 return promptText; 236 } 237 238 /** 239 * Sets the text String to display as the main message in this dialog 240 * 241 * @param promptText the String to be displayed as the main message 242 */ 243 public void setPromptText(String promptText) { 244 this.promptText = promptText; 245 } 246 247 /** 248 * Retrieves the Message element for this dialog 249 * 250 * @return Message the text element containing the message string 251 */ 252 @BeanTagAttribute(name = "prompt", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 253 public MessageField getPrompt() { 254 return prompt; 255 } 256 257 /** 258 * Sets the prompt Message for this dialog 259 * 260 * @param prompt The Message element for this dialog 261 */ 262 public void setPrompt(MessageField prompt) { 263 this.prompt = prompt; 264 } 265 266 /** 267 * Retrieves the explanation InputField used to gather user input text from the dialog 268 * 269 * <p> 270 * By default, the control for this input is configured as a TextAreaControl. It may be configured for 271 * other types of input fields. 272 * </p> 273 * 274 * @return InputField component 275 */ 276 @BeanTagAttribute(name = "explanation", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 277 public InputField getExplanation() { 278 return explanation; 279 } 280 281 /** 282 * Sets the InputField for gathering user text input 283 * 284 * @param explanation InputField 285 */ 286 public void setExplanation(InputField explanation) { 287 this.explanation = explanation; 288 } 289 290 /** 291 * determines if the explanation InputField is to be displayed in this dialog 292 * 293 * <p> 294 * False by default. 295 * </p> 296 * 297 * @return true if this user input is to be rendered, false if not 298 */ 299 @BeanTagAttribute(name = "displayExplanation") 300 public boolean isDisplayExplanation() { 301 return displayExplanation; 302 } 303 304 /** 305 * Sets whether to display the Explanation InputField on this dialog 306 * 307 * @param displayExplanation true if explanation control is to be displayed, false if not 308 */ 309 public void setDisplayExplanation(boolean displayExplanation) { 310 this.displayExplanation = displayExplanation; 311 } 312 313 /** 314 * Gets the choices provided for user response. 315 * 316 * <p> 317 * A List of KeyValue pairs for each of the choices provided on this dialog. 318 * </p> 319 * 320 * @return the List of response actions to provide the user 321 */ 322 @BeanTagAttribute(name = "availableResponses", type = BeanTagAttribute.AttributeType.LISTBEAN) 323 public List<KeyValue> getAvailableResponses() { 324 return availableResponses; 325 } 326 327 /** 328 * Sets the list of user responses to provide on this dialog 329 * 330 * @param availableResponses a List of KeyValue pairs representing the user response choices 331 */ 332 public void setAvailableResponses(List<KeyValue> availableResponses) { 333 this.availableResponses = availableResponses; 334 } 335 336 /** 337 * Retrieves the InputField containing the choices displayed in this dialog 338 * 339 * <p> 340 * By default, this InputField is configured to be a HorizontalCheckboxControl. 341 * Styling is then used to make the checkboxes appear to be buttons. 342 * The values of the availableResponses List are used as labels for the "buttons". 343 * </p> 344 * 345 * @return InputField component within this dialog 346 */ 347 @BeanTagAttribute(name = "responseInputField", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 348 public InputField getResponseInputField() { 349 return responseInputField; 350 } 351 352 /** 353 * Sets the type of InputField used to display the user choices in this dialog 354 * 355 * @param responseInputField a component used to display the response choices 356 */ 357 public void setResponseInputField(InputField responseInputField) { 358 this.responseInputField = responseInputField; 359 } 360 361 /** 362 * Determines the positioning order of the choices displayed on this dialog 363 * 364 * <p> 365 * Some page designers like the positive choice on the left and the negative choice on the right. 366 * Others, prefer just the opposite. This allows the order to easily be switched. 367 * </p> 368 * 369 * @return true if choices left to right 370 * false if choices right to left 371 */ 372 @BeanTagAttribute(name = "reverseButtonOrder") 373 public boolean isReverseButtonOrder() { 374 return reverseButtonOrder; 375 } 376 377 /** 378 * Sets the display order of the choices displayed on this dialog 379 * 380 * <p> 381 * By default, the choices are displayed left to right 382 * </p> 383 * 384 * @param reverseButtonOrder true if buttons displayed left to right, false if right to left 385 */ 386 public void setReverseButtonOrder(boolean reverseButtonOrder) { 387 this.reverseButtonOrder = reverseButtonOrder; 388 } 389 390 /** 391 * Script that will be invoked when the response event is thrown 392 * 393 * <p> 394 * The dialog group will throw a custom event type 'dialogresponse.uif' when a change occurs for the response 395 * input field (for example one of the response options is selected). Script given here will bind to that 396 * event as a handler 397 * </p> 398 * 399 * @return javascript that will execute for the response event 400 */ 401 @BeanTagAttribute(name = "onDialogResponseScript") 402 public String getOnDialogResponseScript() { 403 return onDialogResponseScript; 404 } 405 406 /** 407 * Setter for the 'dialogresponse.uif' event handler code 408 * 409 * @param onDialogResponseScript 410 */ 411 public void setOnDialogResponseScript(String onDialogResponseScript) { 412 this.onDialogResponseScript = onDialogResponseScript; 413 } 414 415 /** 416 * Script that will get invoked when the dialog group is shown 417 * 418 * <p> 419 * Initially a dialog group will either be hidden in the DOM or not present at all (if retrieved via Ajax). 420 * When the dialog is triggered and shown, the 'showdialog.uif' event will be thrown and this script will 421 * be executed 422 * </p> 423 * 424 * @return JavaScript code to execute when the dialog is shown 425 */ 426 @BeanTagAttribute(name = "onShowDialogScript") 427 public String getOnShowDialogScript() { 428 return onShowDialogScript; 429 } 430 431 /** 432 * Setter for the 'showdialog.uif' event handler code 433 * 434 * @param onShowDialogScript 435 */ 436 public void setOnShowDialogScript(String onShowDialogScript) { 437 this.onShowDialogScript = onShowDialogScript; 438 } 439 440 /** 441 * @see org.kuali.rice.krad.uif.component.ComponentBase#copy() 442 */ 443 @Override 444 protected <T> void copyProperties(T component) { 445 super.copyProperties(component); 446 DialogGroup dialogGroupCopy = (DialogGroup) component; 447 448 if (this.availableResponses != null) { 449 dialogGroupCopy.setAvailableResponses(new ArrayList<KeyValue>(this.availableResponses)); 450 } 451 452 dialogGroupCopy.setDisplayExplanation(this.displayExplanation); 453 dialogGroupCopy.setOnDialogResponseScript(this.onDialogResponseScript); 454 dialogGroupCopy.setOnShowDialogScript(this.onShowDialogScript); 455 456 if (this.prompt != null) { 457 dialogGroupCopy.setPrompt((MessageField)this.prompt.copy()); 458 } 459 460 dialogGroupCopy.setPromptText(this.promptText); 461 dialogGroupCopy.setReverseButtonOrder(this.reverseButtonOrder); 462 463 if (this.explanation != null) { 464 dialogGroupCopy.setExplanation((InputField) this.explanation.copy()); 465 } 466 467 if (this.responseInputField != null) { 468 dialogGroupCopy.setResponseInputField((InputField) this.responseInputField.copy()); 469 } 470 } 471}