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}