View Javadoc
1   /**
2    * This file Copyright (c) 2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui;
35  
36  import java.util.Optional;
37  
38  import com.vaadin.event.ShortcutAction;
39  import com.vaadin.event.ShortcutListener;
40  import com.vaadin.shared.ui.ContentMode;
41  import com.vaadin.shared.ui.MarginInfo;
42  import com.vaadin.ui.Alignment;
43  import com.vaadin.ui.Button;
44  import com.vaadin.ui.Component;
45  import com.vaadin.ui.HorizontalLayout;
46  import com.vaadin.ui.Label;
47  import com.vaadin.ui.Notification;
48  import com.vaadin.ui.UI;
49  import com.vaadin.ui.VerticalLayout;
50  import com.vaadin.ui.Window;
51  
52  /**
53   * The {@link AlertBuilder} assists in opening alerts and confirm-dialogs in Magnolia.
54   *
55   * <p>Usage starts from either of the following static methods:
56   * <ul>
57   * <li><i>{@link #confirmDialog(String, Runnable)}</i></li>
58   * <li>or <i>{@link #alert(String)}</i></li>
59   * </ul>
60   *
61   * <p>After that, body, callbacks, level and button captions may be configured through
62   * fluent builder-style methods.
63   *
64   * <p>Finally, the {@link AlertBuilder} has two terminal operations,
65   * both returning a standard Vaadin {@link Window} component, ready to use.
66   * <ul>
67   * <li>{@link #build()} returns the {@link Window} but does not open it;</li>
68   * <li>{@link #buildAndOpen()} returns the {@link Window} and opens it in the current {@link UI}</li>
69   * </ul>
70   *
71   * <p>Example:
72   * <pre>
73   * AlertBuilder.confirmDialog("Say hi?")
74   *         .withLevel(Notification.Type.HUMANIZED_MESSAGE)
75   *         .withBody("Do you want to say hello?")
76   *         .withOkButtonCaption("You bet")
77   *         .withDeclineButtonCaption("No way")
78   *         .withConfirmationHandler(() -> sayHello(name))
79   *         .buildAndOpen();
80   * </pre>
81   */
82  public class AlertBuilder {
83      // TODO candidate for extraction to ResurfaceTheme constants
84      private static final String WINDOW_ALERT = "alert";
85  
86      private final AlertType alertType;
87      private String title;
88      private String body;
89      private Component content;
90      private Notification.Type level = Notification.Type.HUMANIZED_MESSAGE;
91  
92      // fallback values in case none is passed from outside
93      private String confirmButtonCaption = "Yes";
94      private String declineButtonCaption = "No";
95  
96      private Runnable confirmationHandler;
97  
98      public static AlertBuilder confirmDialog(String title) {
99          AlertBuilder confirmDialog = new AlertBuilder(AlertType.CONFIRM_DIALOG);
100         confirmDialog.title = title;
101         return confirmDialog;
102     }
103 
104     public static AlertBuilder confirmDialog(String title, Runnable confirmationHandler) {
105         AlertBuilder confirmDialog = AlertBuilder.confirmDialog(title);
106         confirmDialog.confirmationHandler = confirmationHandler;
107         return confirmDialog;
108     }
109 
110     public static AlertBuilder alert(String title) {
111         AlertBuilder alert = new AlertBuilder(AlertType.ALERT);
112         alert.title = title;
113         return alert;
114     }
115 
116     AlertBuilder(AlertType alertType) {
117         this.alertType = alertType;
118     }
119 
120     public AlertBuilder withTitle(String title) {
121         this.title = title;
122         return this;
123     }
124 
125     public AlertBuilder withBody(String body) {
126         this.body = body;
127         return this;
128     }
129 
130     public AlertBuilder withContent(Component content) {
131         this.content = content;
132         return this;
133     }
134 
135     public AlertBuilder withLevel(Notification.Type level) {
136         this.level = level;
137         return this;
138     }
139 
140     public AlertBuilder withConfirmButtonCaption(String confirmButtonCaption) {
141         this.confirmButtonCaption = confirmButtonCaption;
142         return this;
143     }
144 
145     public AlertBuilder withOkButtonCaption(String okButtonCaption) {
146         this.confirmButtonCaption = okButtonCaption;
147         return this;
148     }
149 
150     public AlertBuilder withDeclineButtonCaption(String declineButtonCaption) {
151         this.declineButtonCaption = declineButtonCaption;
152         return this;
153     }
154 
155     public AlertBuilder withConfirmationHandler(Runnable confirmationHandler) {
156         this.confirmationHandler = confirmationHandler;
157         return this;
158     }
159 
160     public Window build() {
161         Window notificationWindow = new Window(this.title);
162         notificationWindow.addStyleNames(WINDOW_ALERT, this.level.getStyle());
163 
164         notificationWindow.addShortcutListener(new ShortcutListener("close", ShortcutAction.KeyCode.ESCAPE, new int[] {}) {
165             @Override
166             public void handleAction(Object sender, Object target) {
167                 notificationWindow.close();
168             }
169         });
170 
171         notificationWindow.addShortcutListener(new ShortcutListener("confirm", ShortcutAction.KeyCode.ENTER, new int[] {}) {
172             @Override
173             public void handleAction(Object sender, Object target) {
174                 confirmationHandler.run();
175                 notificationWindow.close();
176             }
177         });
178 
179         Button confirmButton = new Button(this.confirmButtonCaption, event -> {
180             Optional.ofNullable(this.confirmationHandler)
181                     .ifPresent(Runnable::run);
182             notificationWindow.close();
183         });
184         confirmButton.addStyleName("confirm");
185 
186         Button declineButton = new Button(this.declineButtonCaption, event -> notificationWindow.close());
187         confirmButton.addStyleName("decline");
188 
189         HorizontalLayout actionLayout = new HorizontalLayout();
190         actionLayout.addStyleName("footer");
191         actionLayout.setWidth("100%");
192         actionLayout.setMargin(new MarginInfo(true, false, false, false));
193 
194         switch (this.alertType) {
195         case CONFIRM_DIALOG:
196             actionLayout.addComponents(declineButton, confirmButton);
197             actionLayout.setComponentAlignment(declineButton, Alignment.MIDDLE_RIGHT);
198             actionLayout.setComponentAlignment(confirmButton, Alignment.MIDDLE_LEFT);
199             break;
200         default:
201             actionLayout.addComponents(confirmButton);
202             actionLayout.setComponentAlignment(confirmButton, Alignment.MIDDLE_CENTER);
203         }
204 
205         notificationWindow.setContent(
206                 new VerticalLayout(
207                         Optional.ofNullable(content).orElseGet(() -> new Label(this.body, ContentMode.HTML)),
208                         actionLayout
209                 )
210         );
211         notificationWindow.center();
212 
213         return notificationWindow;
214     }
215 
216     public Window buildAndOpen() {
217         Window window = this.build();
218         UI.getCurrent().addWindow(window);
219         window.focus();
220         return window;
221     }
222 
223     /**
224      * A simple denominator for Magnolia alerts.
225      *
226      * <p>Alerts only have an OK button; Confirm Dialogs have both Confirm and Decline buttons.
227      */
228     private enum AlertType {
229         ALERT, CONFIRM_DIALOG
230     }
231 }