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