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.data.Item;
46  import com.vaadin.data.fieldgroup.FieldGroup;
47  import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
48  import com.vaadin.data.util.ObjectProperty;
49  import com.vaadin.event.FieldEvents;
50  import com.vaadin.event.LayoutEvents.LayoutClickEvent;
51  import com.vaadin.event.LayoutEvents.LayoutClickListener;
52  import com.vaadin.event.ShortcutAction.KeyCode;
53  import com.vaadin.event.ShortcutListener;
54  import com.vaadin.ui.AbstractSelect;
55  import com.vaadin.ui.Button;
56  import com.vaadin.ui.Button.ClickEvent;
57  import com.vaadin.ui.Button.ClickListener;
58  import com.vaadin.ui.ComboBox;
59  import com.vaadin.ui.CssLayout;
60  import com.vaadin.ui.CustomComponent;
61  import com.vaadin.ui.FormLayout;
62  import com.vaadin.ui.Label;
63  import com.vaadin.ui.TabSheet;
64  import com.vaadin.ui.TabSheet.SelectedTabChangeEvent;
65  import com.vaadin.ui.TabSheet.SelectedTabChangeListener;
66  import com.vaadin.ui.TextField;
67  import com.vaadin.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.setImmediate(false);
275             group.setNewItemHandler(new AbstractSelect.NewItemHandler() {
276                 @Override
277                 public void addNewItem(String newItemCaption) {
278                     String newGroupId = Path.getValidatedLabel(newItemCaption);
279                     group.addItem(newGroupId);
280                     group.setItemCaption(newGroupId, newItemCaption);
281                     group.setValue(newGroupId);
282                 }
283             });
284             // the blur-listener below ensures "appropriate" behavior when adding a new value and then clicking tab -> to blur -> to go to add-button
285             // without the listener the new value is NOT selected and sometimes not added (with tab, enter)
286             // String value = event.getSource().toString() leads to a warning in the log-file and is discourage from VAADIN
287             group.addBlurListener(new FieldEvents.BlurListener() {
288                 @Override
289                 public void blur(FieldEvents.BlurEvent event) {
290                     String value = (String) group.getValue();
291                     group.setValue(value);
292                 }
293             });
294 
295             group.setDescription(i18n.translate("favorites.form.groups"));
296             layout.addComponent(group);
297 
298             // Bind fields individually — binding members doesn't seem to work with initial (null) datasource
299             form = new FieldGroup();
300             form.bind(title, "title");
301             form.bind(url, "url");
302             form.bind(group, "group");
303 
304             final CssLayout buttons = new CssLayout();
305             buttons.addStyleName("buttons");
306 
307             final Button addButton = new Button(i18n.translate("favorites.button.add"), new ClickListener() {
308 
309                 @Override
310                 public void buttonClick(ClickEvent event) {
311                     addFavorite();
312                 }
313             });
314             addButton.addStyleName("commit");
315             buttons.addComponent(addButton);
316             layout.addComponent(buttons);
317 
318             this.enterShortcutListener = new ShortcutListener("", KeyCode.ENTER, null) {
319 
320                 @Override
321                 public void handleAction(Object sender, Object target) {
322                     addFavorite();
323                 }
324             };
325 
326             setCompositionRoot(layout);
327         }
328 
329         void setDataSource(Item newFavorite) {
330             form.setItemDataSource(newFavorite);
331         }
332 
333         void setAvailableGroups(Map<String, String> availableGroups) {
334             group.removeAllItems();
335             for (Entry<String, String> entry : availableGroups.entrySet()) {
336                 String id = entry.getKey();
337                 group.addItem(id);
338                 group.setItemCaption(id, entry.getValue());
339             }
340             this.availableGroups = availableGroups;
341         }
342 
343         public void addEnterKeyShortcutListener() {
344             addShortcutListener(enterShortcutListener);
345             title.focus();
346         }
347 
348         public void removeEnterKeyShortcutListener() {
349             removeShortcutListener(enterShortcutListener);
350         }
351 
352         private void addFavorite() {
353             try {
354                 JcrNewNodeAdapter newFavorite = (JcrNewNodeAdapter) form.getItemDataSource();
355                 form.commit();
356 
357                 // since MGNLUI-2599 it is possible to add a group and a favorite (which then goes into the group) at the same time
358                 if (group == null || group.getValue() == null || !selectedGroupIsNew()) {
359                     listener.addFavorite(newFavorite);
360                 } else {
361                     JcrNewNodeAdapter newGroup = ((FavoritesPresenter) listener).createNewGroupSuggestion();
362                     String newGroupId = (String) group.getValue();
363                     String newGroupLabel = group.getItemCaption(newGroupId);
364                     // must set the properties for the group here manually; properties of newFavorite are set in binder
365                     newGroup.addItemProperty("group", new ObjectProperty<String>(newGroupId));
366                     newGroup.addItemProperty("title", new ObjectProperty<String>(newGroupLabel));
367                     listener.addFavoriteAndGroup(newFavorite, newGroup);
368                 }
369                 close(); // close form explicitly upon successful submit
370             } catch (CommitException e) {
371                 shell.openNotification(MessageStyleTypeEnum.ERROR, true, i18n.translate("favorites.fields.required"));
372             }
373         }
374 
375         private boolean selectedGroupIsNew() {
376             if (availableGroups == null || availableGroups.size() == 0) {
377                 return true;
378             } else {
379                 Object value = group.getValue();
380                 if (value != null && availableGroups != null && availableGroups.size() > 0) {
381                     return !availableGroups.containsKey(value);
382                 }
383             }
384             return false;
385         }
386 
387     }
388 
389     /**
390      * The form component displayed in the group tab.
391      */
392     private class InternalFavoriteGroupForm extends CustomComponent {
393 
394         private TextField title = new TextField(i18n.translate("favorites.form.title"));
395 
396         private ShortcutListener enterShortcutListener;
397         private final FieldGroup form;
398 
399         public InternalFavoriteGroupForm() {
400             addStyleName("favorites-form-content");
401             FormLayout layout = new FormLayout();
402 
403             title.setRequired(true);
404             title.setDescription(i18n.translate("favorites.form.title"));// tooltip
405             title.addStyleName("group-title");
406             layout.addComponent(title);
407 
408             // Bind fields individually — binding members doesn't seem to work with initial (null) datasource
409             form = new FieldGroup();
410             form.bind(title, "title");
411 
412             final CssLayout buttons = new CssLayout();
413             buttons.addStyleName("buttons");
414 
415             final Button addButton = new Button(i18n.translate("favorites.button.add"), new ClickListener() {
416 
417                 @Override
418                 public void buttonClick(ClickEvent event) {
419                     addGroup();
420                 }
421             });
422             addButton.addStyleName("v-button-commit");
423             buttons.addComponent(addButton);
424             layout.addComponent(buttons);
425 
426             this.enterShortcutListener = new ShortcutListener("", KeyCode.ENTER, null) {
427 
428                 @Override
429                 public void handleAction(Object sender, Object target) {
430                     addGroup();
431                 }
432             };
433 
434             setCompositionRoot(layout);
435         }
436 
437         void setDataSource(Item newGroup) {
438             form.setItemDataSource(newGroup);
439         }
440 
441         public void addEnterKeyShortcutListener() {
442             addShortcutListener(enterShortcutListener);
443             title.focus();
444         }
445 
446         public void removeEnterKeyShortcutListener() {
447             removeShortcutListener(enterShortcutListener);
448         }
449 
450         private void addGroup() {
451             try {
452                 JcrNewNodeAdapter newGroup = (JcrNewNodeAdapter) form.getItemDataSource();
453                 form.commit();
454                 listener.addGroup(newGroup);
455                 close(); // close form explicitly upon successful submit
456             } catch (CommitException e) {
457                 shell.openNotification(MessageStyleTypeEnum.ERROR, true, i18n.translate("favorites.fields.required"));
458             }
459         }
460     }
461 }