View Javadoc
1   /**
2    * This file Copyright (c) 2013-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.admincentral.shellapp.favorites;
35  
36  import info.magnolia.cms.core.Path;
37  import info.magnolia.i18nsystem.SimpleTranslator;
38  import info.magnolia.ui.api.shell.Shell;
39  import info.magnolia.ui.vaadin.integration.jcr.JcrNewNodeAdapter;
40  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
41  
42  import java.util.Map;
43  import java.util.Map.Entry;
44  
45  import com.vaadin.event.FieldEvents;
46  import com.vaadin.event.LayoutEvents.LayoutClickEvent;
47  import com.vaadin.event.LayoutEvents.LayoutClickListener;
48  import com.vaadin.event.ShortcutAction.KeyCode;
49  import com.vaadin.event.ShortcutListener;
50  import com.vaadin.ui.Button;
51  import com.vaadin.ui.Button.ClickEvent;
52  import com.vaadin.ui.Button.ClickListener;
53  import com.vaadin.ui.CssLayout;
54  import com.vaadin.ui.CustomComponent;
55  import com.vaadin.ui.FormLayout;
56  import com.vaadin.ui.TabSheet;
57  import com.vaadin.ui.TabSheet.SelectedTabChangeEvent;
58  import com.vaadin.ui.TabSheet.SelectedTabChangeListener;
59  import com.vaadin.v7.data.Item;
60  import com.vaadin.v7.data.fieldgroup.FieldGroup;
61  import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException;
62  import com.vaadin.v7.data.util.ObjectProperty;
63  import com.vaadin.v7.ui.AbstractSelect;
64  import com.vaadin.v7.ui.ComboBox;
65  import com.vaadin.v7.ui.Label;
66  import com.vaadin.v7.ui.TextField;
67  import com.vaadin.v7.ui.VerticalLayout;
68  
69  /**
70   * FavoritesForm.
71   */
72  public final class FavoritesForm extends CustomComponent {
73  
74      private static final String EDIT_ACTION_STYLENAME = "favItemEdit";
75  
76      private FavoritesView.Listener listener;
77      private Shell shell;
78      private TabSheet tabsheet;
79      private Label arrowIcon;
80      private InternalFavoriteEntryForm favoriteEntryForm;
81      private InternalFavoriteGroupForm favoriteGroupForm;
82      private final SimpleTranslator i18n;
83  
84      private Label editIcon;
85      private Label editLabel;
86  
87      public FavoritesForm(FavoritesView.Listener listener, Shell shell, SimpleTranslator i18n) {
88          this.listener = listener;
89          this.shell = shell;
90          this.i18n = i18n;
91          init();
92      }
93  
94      /**
95       * Since 5.3.5 — use {@link #FavoritesForm(info.magnolia.ui.admincentral.shellapp.favorites.FavoritesView.Listener, Shell, SimpleTranslator)} as a constructor and populate fav/group entries individually through the new setters.
96       *
97       * @see #setNewFavorite(Item)
98       * @see #setNewGroup(Item)
99       * @see #setAvailableGroups(Map)
100      */
101     @Deprecated
102     public FavoritesForm(final JcrNewNodeAdapter newFavorite, final JcrNewNodeAdapter newGroup, final Map<String, String> availableGroups,
103             final FavoritesView.Listener listener, final Shell shell, final SimpleTranslator i18n) {
104         this(listener, shell, i18n);
105     }
106 
107     private void init() {
108         addStyleName("favorites-form");
109         final VerticalLayout favoriteForm = new VerticalLayout();
110         favoriteEntryForm = new InternalFavoriteEntryForm();
111         favoriteGroupForm = new InternalFavoriteGroupForm();
112 
113         tabsheet = new TabSheet();
114         tabsheet.addStyleName("favorites-tabs");
115         tabsheet.addTab(favoriteEntryForm, i18n.translate("favorites.form.favorite.add"));
116         tabsheet.addTab(favoriteGroupForm, i18n.translate("favorites.form.group.add"));
117 
118         tabsheet.addSelectedTabChangeListener(new SelectedTabChangeListener() {
119 
120             @Override
121             public void selectedTabChange(SelectedTabChangeEvent event) {
122                 if (event.getTabSheet().getSelectedTab() instanceof InternalFavoriteEntryForm) {
123                     favoriteGroupForm.removeEnterKeyShortcutListener();
124                     favoriteEntryForm.addEnterKeyShortcutListener();
125                 } else {
126                     favoriteEntryForm.removeEnterKeyShortcutListener();
127                     favoriteGroupForm.addEnterKeyShortcutListener();
128                 }
129             }
130         });
131 
132         final CssLayout header = new CssLayout();
133         header.addStyleName("dialog-header");
134         header.setSizeFull();
135         header.addLayoutClickListener(new LayoutClickListener() {
136             @Override
137             public void layoutClick(LayoutClickEvent event) {
138                 // change the visibility of the group- and favorite-items
139                 if (event.getClickedComponent() == editIcon || event.getChildComponent() == editLabel) {
140                     if (!listener.hasItems() || listener.itemsAreEditable()) {
141                         listener.setToInitialState();
142                     } else {
143                         listener.setItemsEditable(true);
144                     }
145                 } else {
146                     // just open || close the FavoritesForm
147                     if (isOpen()) {
148                         close();
149                     } else {
150                         open();
151                     }
152                 }
153             }
154         });
155 
156         // add
157         final Label addNewIcon = new Label();
158         addNewIcon.setSizeUndefined();
159         addNewIcon.addStyleName("icon");
160         addNewIcon.addStyleName("icon-add-fav");
161         final Label addNewLabel = new Label(i18n.translate("favorites.form.add"));
162         addNewLabel.setSizeUndefined();
163         addNewLabel.addStyleName("title");
164 
165         // edit
166         editIcon = new Label();
167         editIcon.setSizeUndefined();
168         editIcon.addStyleName(EDIT_ACTION_STYLENAME);
169         editIcon.addStyleName("icon");
170         editIcon.addStyleName("icon-edit");
171 
172         editLabel = new Label(i18n.translate("favorites.form.favorite.edit"));
173         editLabel.setSizeUndefined();
174         editLabel.addStyleName("title");
175         editLabel.addStyleName(EDIT_ACTION_STYLENAME);
176 
177         // arrow
178         arrowIcon = new Label();
179         arrowIcon.setSizeUndefined();
180         arrowIcon.addStyleName("icon");
181         arrowIcon.addStyleName("arrow");
182         arrowIcon.addStyleName("icon-arrow2_n");
183 
184         // assemble
185         header.addComponent(addNewIcon);
186         header.addComponent(addNewLabel);
187         header.addComponent(editIcon);
188         header.addComponent(editLabel);
189         header.addComponent(arrowIcon);
190         favoriteForm.addComponent(header);
191         favoriteForm.addComponent(tabsheet);
192 
193         // form is closed initially
194         close();
195         setCompositionRoot(favoriteForm);
196     }
197 
198     public void setNewFavorite(Item newFavorite) {
199         favoriteEntryForm.setDataSource(newFavorite);
200     }
201 
202     public void setNewGroup(Item newGroup) {
203         favoriteGroupForm.setDataSource(newGroup);
204     }
205 
206     public void setAvailableGroups(Map<String, String> availableGroups) {
207         favoriteEntryForm.setAvailableGroups(availableGroups);
208     }
209 
210     public void setEditActionEnabled(boolean enabled) {
211         if (!enabled) {
212             editIcon.addStyleName("disabled");
213             editLabel.addStyleName("disabled");
214         } else {
215             editIcon.removeStyleName("disabled");
216             editLabel.removeStyleName("disabled");
217         }
218     }
219 
220     public void close() {
221         tabsheet.setVisible(false);
222         // reset form to its first tab when it is closed
223         if (tabsheet.getSelectedTab() != favoriteEntryForm) {
224             tabsheet.setSelectedTab(favoriteEntryForm);
225         }
226         arrowIcon.removeStyleName("icon-arrow2_s");
227         arrowIcon.addStyleName("icon-arrow2_n");
228         // remove key shortcut listener or this might compete with the next element getting the focus.
229         favoriteEntryForm.removeEnterKeyShortcutListener();
230         favoriteGroupForm.removeEnterKeyShortcutListener();
231     }
232 
233     public void open() {
234         tabsheet.setVisible(true);
235         arrowIcon.removeStyleName("icon-arrow2_n");
236         arrowIcon.addStyleName("icon-arrow2_s");
237         favoriteEntryForm.addEnterKeyShortcutListener();
238         // the group form will get the key shortcut listener attached on selecting it.
239     }
240 
241     public boolean isOpen() {
242         return tabsheet.isVisible();
243     }
244 
245     /**
246      * The form component displayed in the favorite tab.
247      */
248     private class InternalFavoriteEntryForm extends CustomComponent {
249 
250         private TextField url = new TextField(i18n.translate("favorites.form.location"));
251         private TextField title = new TextField(i18n.translate("favorites.form.title"));
252         private ComboBox group;
253 
254         private ShortcutListener enterShortcutListener;
255 
256         private final FieldGroup form;
257 
258         private Map<String, String> availableGroups;
259 
260         public InternalFavoriteEntryForm() {
261             addStyleName("favorites-form-content");
262             FormLayout layout = new FormLayout();
263 
264             title.setRequired(true);
265             title.setDescription(i18n.translate("favorites.form.title")); // tooltip
266 
267             url.setRequired(true);
268             url.setDescription(i18n.translate("favorites.form.location"));
269             layout.addComponent(title);
270             layout.addComponent(url);
271 
272             group = new ComboBox(i18n.translate("favorites.form.groups"));
273             group.setNewItemsAllowed(true);
274             group.setNewItemHandler(new AbstractSelect.NewItemHandler() {
275                 @Override
276                 public void addNewItem(String newItemCaption) {
277                     String newGroupId = Path.getValidatedLabel(newItemCaption);
278                     group.addItem(newGroupId);
279                     group.setItemCaption(newGroupId, newItemCaption);
280                     group.setValue(newGroupId);
281                 }
282             });
283             // the blur-listener below ensures "appropriate" behavior when adding a new value and then clicking tab -> to blur -> to go to add-button
284             // without the listener the new value is NOT selected and sometimes not added (with tab, enter)
285             // String value = event.getSource().toString() leads to a warning in the log-file and is discourage from VAADIN
286             group.addBlurListener((FieldEvents.BlurListener) event -> {
287                 String value = (String) group.getValue();
288                 group.setValue(value);
289             });
290 
291             group.setDescription(i18n.translate("favorites.form.groups"));
292             layout.addComponent(group);
293 
294             // Bind fields individually — binding members doesn't seem to work with initial (null) datasource
295             form = new FieldGroup();
296             form.bind(title, "title");
297             form.bind(url, "url");
298             form.bind(group, "group");
299 
300             final CssLayout buttons = new CssLayout();
301             buttons.addStyleName("buttons");
302 
303             final Button addButton = new Button(i18n.translate("favorites.button.add"), new ClickListener() {
304 
305                 @Override
306                 public void buttonClick(ClickEvent event) {
307                     addFavorite();
308                 }
309             });
310             addButton.addStyleName("commit");
311             buttons.addComponent(addButton);
312             layout.addComponent(buttons);
313 
314             this.enterShortcutListener = new ShortcutListener("", KeyCode.ENTER, null) {
315 
316                 @Override
317                 public void handleAction(Object sender, Object target) {
318                     addFavorite();
319                 }
320             };
321 
322             setCompositionRoot(layout);
323         }
324 
325         void setDataSource(Item newFavorite) {
326             form.setItemDataSource(newFavorite);
327         }
328 
329         void setAvailableGroups(Map<String, String> availableGroups) {
330             group.removeAllItems();
331             for (Entry<String, String> entry : availableGroups.entrySet()) {
332                 String id = entry.getKey();
333                 group.addItem(id);
334                 group.setItemCaption(id, entry.getValue());
335             }
336             this.availableGroups = availableGroups;
337         }
338 
339         public void addEnterKeyShortcutListener() {
340             addShortcutListener(enterShortcutListener);
341             title.focus();
342         }
343 
344         public void removeEnterKeyShortcutListener() {
345             removeShortcutListener(enterShortcutListener);
346         }
347 
348         private void addFavorite() {
349             try {
350                 JcrNewNodeAdapter newFavorite = (JcrNewNodeAdapter) form.getItemDataSource();
351                 form.commit();
352 
353                 // since MGNLUI-2599 it is possible to add a group and a favorite (which then goes into the group) at the same time
354                 if (group == null || group.getValue() == null || !selectedGroupIsNew()) {
355                     listener.addFavorite(newFavorite);
356                 } else {
357                     JcrNewNodeAdapter newGroup = ((FavoritesPresenter) listener).createNewGroupSuggestion();
358                     String newGroupId = (String) group.getValue();
359                     String newGroupLabel = group.getItemCaption(newGroupId);
360                     // must set the properties for the group here manually; properties of newFavorite are set in binder
361                     newGroup.addItemProperty("group", new ObjectProperty<String>(newGroupId));
362                     newGroup.addItemProperty("title", new ObjectProperty<String>(newGroupLabel));
363                     listener.addFavoriteAndGroup(newFavorite, newGroup);
364                 }
365                 close(); // close form explicitly upon successful submit
366             } catch (CommitException e) {
367                 shell.openNotification(MessageStyleTypeEnum.ERROR, true, i18n.translate("favorites.fields.required"));
368             }
369         }
370 
371         private boolean selectedGroupIsNew() {
372             if (availableGroups == null || availableGroups.size() == 0) {
373                 return true;
374             } else {
375                 Object value = group.getValue();
376                 if (value != null && availableGroups != null && availableGroups.size() > 0) {
377                     return !availableGroups.containsKey(value);
378                 }
379             }
380             return false;
381         }
382 
383     }
384 
385     /**
386      * The form component displayed in the group tab.
387      */
388     private class InternalFavoriteGroupForm extends CustomComponent {
389 
390         private TextField title = new TextField(i18n.translate("favorites.form.title"));
391 
392         private ShortcutListener enterShortcutListener;
393         private final FieldGroup form;
394 
395         public InternalFavoriteGroupForm() {
396             addStyleName("favorites-form-content");
397             FormLayout layout = new FormLayout();
398 
399             title.setRequired(true);
400             title.setDescription(i18n.translate("favorites.form.title"));// tooltip
401             title.addStyleName("group-title");
402             layout.addComponent(title);
403 
404             // Bind fields individually — binding members doesn't seem to work with initial (null) datasource
405             form = new FieldGroup();
406             form.bind(title, "title");
407 
408             final CssLayout buttons = new CssLayout();
409             buttons.addStyleName("buttons");
410 
411             final Button addButton = new Button(i18n.translate("favorites.button.add"), new ClickListener() {
412 
413                 @Override
414                 public void buttonClick(ClickEvent event) {
415                     addGroup();
416                 }
417             });
418             addButton.addStyleName("v-button-commit");
419             buttons.addComponent(addButton);
420             layout.addComponent(buttons);
421 
422             this.enterShortcutListener = new ShortcutListener("", KeyCode.ENTER, null) {
423 
424                 @Override
425                 public void handleAction(Object sender, Object target) {
426                     addGroup();
427                 }
428             };
429 
430             setCompositionRoot(layout);
431         }
432 
433         void setDataSource(Item newGroup) {
434             form.setItemDataSource(newGroup);
435         }
436 
437         public void addEnterKeyShortcutListener() {
438             addShortcutListener(enterShortcutListener);
439             title.focus();
440         }
441 
442         public void removeEnterKeyShortcutListener() {
443             removeShortcutListener(enterShortcutListener);
444         }
445 
446         private void addGroup() {
447             try {
448                 JcrNewNodeAdapter newGroup = (JcrNewNodeAdapter) form.getItemDataSource();
449                 form.commit();
450                 listener.addGroup(newGroup);
451                 close(); // close form explicitly upon successful submit
452             } catch (CommitException e) {
453                 shell.openNotification(MessageStyleTypeEnum.ERROR, true, i18n.translate("favorites.fields.required"));
454             }
455         }
456     }
457 }