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}