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.i18nsystem.SimpleTranslator;
37  import info.magnolia.ui.api.overlay.ConfirmationCallback;
38  import info.magnolia.ui.api.shell.Shell;
39  import info.magnolia.ui.framework.AdmincentralNodeTypes;
40  import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
41  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
42  
43  import java.util.Date;
44  
45  import org.apache.commons.lang3.StringUtils;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import com.vaadin.event.FieldEvents.BlurEvent;
50  import com.vaadin.event.FieldEvents.BlurListener;
51  import com.vaadin.event.FieldEvents.FocusEvent;
52  import com.vaadin.event.FieldEvents.FocusListener;
53  import com.vaadin.event.LayoutEvents.LayoutClickEvent;
54  import com.vaadin.event.LayoutEvents.LayoutClickListener;
55  import com.vaadin.event.ShortcutListener;
56  import com.vaadin.ui.Button.ClickEvent;
57  import com.vaadin.ui.Button.ClickListener;
58  import com.vaadin.ui.CustomComponent;
59  import com.vaadin.ui.NativeButton;
60  import com.vaadin.v7.shared.ui.label.ContentMode;
61  import com.vaadin.v7.ui.HorizontalLayout;
62  import com.vaadin.v7.ui.Label;
63  import com.vaadin.v7.ui.TextField;
64  
65  /**
66   * FavoritesEntry.
67   */
68  public final class FavoritesEntry extends CustomComponent implements EditableFavoriteItem, EditingEvent.EditingNotifier {
69  
70      private static final Logger log = LoggerFactory.getLogger(FavoritesEntry.class);
71      private static long uniqueIdCounter = 23;
72  
73      private HorizontalLayout root = new HorizontalLayout();
74      private String location;
75      private String title;
76      private String group = null;
77      private String nodename;
78      private TextField titleField;
79      private NativeButton editButton;
80      private NativeButton removeButton;
81      private boolean editable;
82      private EnterKeyShortcutListener enterKeyShortcutListener;
83      private EscapeKeyShortcutListener escapeKeyShortcutListener;
84      private Shell shell;
85      private final SimpleTranslator i18n;
86      private String itemId;
87      private FavoritesView.Listener listener;
88  
89      public FavoritesEntry(final AbstractJcrNodeAdapter favorite, final FavoritesView.Listener listener, final Shell shell, SimpleTranslator i18n) {
90          super();
91          this.listener = listener;
92          this.shell = shell;
93          this.i18n = i18n;
94          itemId = createItemId(favorite);
95          construct(favorite, listener);
96      }
97  
98      @Override
99      public String getItemId() {
100         return itemId;
101     }
102 
103     public String getRelPath() {
104         return StringUtils.isBlank(group) ? this.nodename : this.group + "/" + this.nodename;
105     }
106 
107     public String getNodename() {
108         return this.nodename;
109     }
110 
111     public void setGroup(String group) {
112         this.group = group;
113     }
114 
115     public String getGroup() {
116         return this.group;
117     }
118 
119     @Override
120     public void setToNonEditableState() {
121         setEditable(false);
122     }
123 
124     private void setEditable(boolean editable) {
125         this.editable = editable;
126         String icon = "icon-tick";
127         if (editable) {
128             listener.setCurrentEditedItemId(getItemId());
129             titleField.addStyleName("editable");
130             titleField.focus();
131             titleField.selectAll();
132 
133         } else {
134             icon = "icon-edit";
135             titleField.removeStyleName("editable");
136             // pending changes are reverted
137             titleField.setValue(title);
138         }
139         titleField.setReadOnly(!editable);
140         editButton.setCaption("<span class=\"" + icon + "\"></span>");
141         fireEvent(new EditingEvent(this, editable));
142     }
143 
144 
145     private void construct(final AbstractJcrNodeAdapter favorite, final FavoritesView.Listener listener) {
146         addStyleName("favorites-entry");
147         setSizeUndefined();
148         root.setSizeUndefined();
149 
150         this.enterKeyShortcutListener = new EnterKeyShortcutListener(listener);
151         this.escapeKeyShortcutListener = new EscapeKeyShortcutListener();
152 
153         this.nodename = favorite.getNodeName();
154         this.location = favorite.getItemProperty(AdmincentralNodeTypes.Favorite.URL).getValue().toString();
155         this.title = favorite.getItemProperty(AdmincentralNodeTypes.Favorite.TITLE).getValue().toString();
156 
157         String icon = "icon-app";
158         if (favorite.getItemProperty(AdmincentralNodeTypes.Favorite.ICON).getValue() != null) {
159             icon = favorite.getItemProperty(AdmincentralNodeTypes.Favorite.ICON).getValue().toString();
160         }
161 
162         final Label iconLabel = new Label();
163         iconLabel.setValue("<span class=\"" + icon + "\"></span>");
164         iconLabel.setStyleName("icon");
165         iconLabel.setContentMode(ContentMode.HTML);
166         root.addComponent(iconLabel);
167 
168         titleField = new TextField();
169         titleField.setValue(title);
170         titleField.setReadOnly(true);
171 
172         titleField.addFocusListener(new FocusListener() {
173 
174             @Override
175             public void focus(FocusEvent event) {
176                 iconLabel.removeShortcutListener(enterKeyShortcutListener);
177                 titleField.addShortcutListener(enterKeyShortcutListener);
178                 titleField.addShortcutListener(escapeKeyShortcutListener);
179             }
180         });
181 
182         titleField.addBlurListener(new BlurListener() {
183 
184             @Override
185             public void blur(BlurEvent event) {
186                 titleField.removeShortcutListener(enterKeyShortcutListener);
187                 titleField.removeShortcutListener(escapeKeyShortcutListener);
188             }
189         });
190 
191         root.addComponent(titleField);
192 
193         editButton = new NativeButton();
194         editButton.setHtmlContentAllowed(true);
195         editButton.setCaption("<span class=\"icon-edit\"></span>");
196         editButton.addStyleName("favorite-action");
197         editButton.addClickListener(new ClickListener() {
198 
199             @Override
200             public void buttonClick(ClickEvent event) {
201                 doEditTitle(listener);
202             }
203         });
204         editButton.setVisible(false);
205         root.addComponent(editButton);
206 
207         removeButton = new NativeButton();
208         removeButton.setHtmlContentAllowed(true);
209         removeButton.setCaption("<span class=\"icon-trash\"></span>");
210         removeButton.addStyleName("favorite-action");
211         removeButton.addClickListener(new ClickListener() {
212 
213             @Override
214             public void buttonClick(ClickEvent event) {
215                 shell.openConfirmation(MessageStyleTypeEnum.WARNING, i18n.translate("confirmation.delete.title.generic"), i18n.translate("confirmation.cannot.undo"), i18n.translate("confirmation.delete.yes"), i18n.translate("confirmation.no"), false, new ConfirmationCallback() {
216 
217                     @Override
218                     public void onSuccess() {
219                         listener.removeFavorite(getRelPath());
220                     }
221 
222                     @Override
223                     public void onCancel() {
224                         // no op
225                     }
226                 });
227             }
228         });
229         removeButton.setVisible(false);
230         root.addComponent(removeButton);
231 
232         root.addLayoutClickListener(new LayoutClickListener() {
233             @Override
234             public void layoutClick(LayoutClickEvent event) {
235                 if (event.getClickedComponent() == titleField && !editable) {
236                     if (event.isDoubleClick()) {
237                         // TODO fgrilli commented out as, besides making the text editable, it also goes to the saved location
238                         // See MGNLUI-1317
239                     } else {
240                         listener.goToLocation(location);
241                     }
242                 }
243             }
244         });
245 
246         setCompositionRoot(root);
247         setIconsVisibility(false);
248     }
249 
250     @Override
251     public void addEditingListener(EditingEvent.EditingListener listener) {
252         addListener("onEdit", EditingEvent.class, listener, EditingEvent.EDITING_METHOD);
253     }
254 
255     @Override
256     public void removeEditingListener(EditingEvent.EditingListener listener) {
257         removeListener(EditingEvent.class, listener, EditingEvent.EDITING_METHOD);
258     }
259 
260     @Override
261     public void setIconsVisibility(boolean visible) {
262         editButton.setVisible(visible);
263         removeButton.setVisible(visible);
264     }
265 
266     @Override
267     public boolean iconsAreVisible() {
268         return editButton.isVisible();
269     }
270 
271     private void doEditTitle(final FavoritesView.Listener listener) {
272         if (StringUtils.isBlank(titleField.getValue())) {
273             shell.openNotification(MessageStyleTypeEnum.ERROR, true, i18n.translate("favorites.title.required"));
274             titleField.focus();
275             return;
276         }
277 
278         if (editable) {
279             boolean titleHasChanged = !title.equals(titleField.getValue());
280             if (titleHasChanged) {
281                 listener.editFavorite(getRelPath(), titleField.getValue());
282             }
283             setEditable(false);
284         } else {
285             setEditable(true);
286         }
287     }
288 
289     private class EnterKeyShortcutListener extends ShortcutListener {
290         private FavoritesView.Listener listener;
291 
292         public EnterKeyShortcutListener(final FavoritesView.Listener listener) {
293             super("", KeyCode.ENTER, null);
294             this.listener = listener;
295         }
296 
297         @Override
298         public void handleAction(Object sender, Object target) {
299             if (editable) {
300                 doEditTitle(listener);
301             } else {
302                 setIconsVisibility(true);
303             }
304         }
305     }
306 
307     private class EscapeKeyShortcutListener extends ShortcutListener {
308 
309         public EscapeKeyShortcutListener() {
310             super("", KeyCode.ESCAPE, null);
311         }
312 
313         @Override
314         public void handleAction(Object sender, Object target) {
315             setToNonEditableState();
316         }
317     }
318 
319     /**
320      * Creates a unique ID to use for {@code EditableFavoriteItem}s.
321      * @deprecated since 5.4.7 - use {@link #createItemId(AbstractJcrNodeAdapter)} instead.
322      */
323     @Deprecated
324     public static String createItemdId(AbstractJcrNodeAdapter nodeAdapter) {
325         return createItemId(nodeAdapter);
326     }
327 
328     /**
329      * Creates a unique ID to use for {@code EditableFavoriteItem}s.
330      */
331     public static String createItemId(AbstractJcrNodeAdapter nodeAdapter) {
332         String id = null;
333         try {
334             id = nodeAdapter.getItemId().getUuid();
335         } catch (Exception ex) {
336             log.error("Failed to create an itemId from an AbstractJcrNodeAdapter", ex);
337         } finally {
338             if (id == null) {
339                 uniqueIdCounter++;
340                 id = String.valueOf((new Date()).getTime() + uniqueIdCounter);
341             }
342         }
343         return id;
344     }
345 
346 }