View Javadoc
1   /**
2    * Copyright 2005-2016 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 org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.util.KeyValue;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.UifParameters;
26  import org.kuali.rice.krad.uif.UifPropertyPaths;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.element.Action;
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.lifecycle.ViewLifecycleRestriction;
32  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
33  import org.kuali.rice.krad.uif.util.ComponentFactory;
34  import org.kuali.rice.krad.uif.util.LifecycleElement;
35  import org.kuali.rice.krad.uif.widget.QuickFinder;
36  
37  import java.util.ArrayList;
38  import java.util.List;
39  
40  /**
41   * Special type of group that presents the content in a modal dialog.
42   *
43   * <p>A dialog group can be used for many different purposes. First it can be used to give a simple confirmation (
44   * a prompt with ok/cancel or yes/no options). The {@link org.kuali.rice.krad.uif.element.Action} component contains
45   * properties for adding a confirmation dialog. Next, a dialog can be used to prompt for a response or to gather
46   * addition data on the client. In this situation, the dialog is configured either in the view or external to the view,
47   * and the developers triggers the display of the dialog using the javascript method showDialog. See krad.modal.js
48   * for more information. Dialogs can also be triggered from a controller method (or other piece of server code). Again
49   * the dialog is configured with the view or external to the view, and the controller method triggers the show using
50   * the method {@link org.kuali.rice.krad.web.controller.UifControllerBase#showDialog}.</p>
51   *
52   * <p>A dialog is a group and can be configured like any other general group. For building basic dialogs, there are
53   * convenience properties that can be used. In addition, there are base beans provided with definitions for these
54   * properties. This includes a basic prompt message and responses. Note to have responses with different action
55   * properties,
56   * set the items of the dialog groups footer directly.</p>
57   *
58   * @author Kuali Rice Team (rice.collab@kuali.org)
59   */
60  @BeanTags({@BeanTag(name = "dialog", parent = "Uif-DialogGroup"),
61          @BeanTag(name = "dialogOkCancel", parent = "Uif-DialogGroup-OkCancel"),
62          @BeanTag(name = "dialogOkCancelExpl", parent = "Uif-DialogGroup-OkCancelExpl"),
63          @BeanTag(name = "dialogYesNo", parent = "Uif-DialogGroup-YesNo"),
64          @BeanTag(name = "actionConfirmation", parent = "Uif-ActionConfirmation"),
65          @BeanTag(name = "actionConfirmationExpl", parent = "Uif-ActionConfirmationExpl")})
66  public class DialogGroup extends GroupBase {
67  
68      private static final long serialVersionUID = 1L;
69  
70      private MessageField prompt;
71      private InputField explanation;
72  
73      private List<KeyValue> availableResponses;
74  
75      private String dialogCssClass;
76  
77      private String onDialogResponseScript;
78      private String onShowDialogScript;
79      private String onHideDialogScript;
80      private String onHiddenDialogScript;
81  
82      private boolean destroyDialogOnHidden;
83  
84      /**
85       * Default Constructor.
86       */
87      public DialogGroup() {
88          super();
89      }
90  
91      /**
92       * The following actions are performed in this phase:
93       *
94       * <ul>
95       * <li>If property name nor binding path is set on the explanation field, sets to generic form property</li>
96       * <li>Move custom dialogGroup properties prompt and explanation into items collection if the
97       * items list is not already populated</li>
98       * </ul>
99       *
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 }