View Javadoc

1   /**
2    * This file Copyright (c) 2013 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.i18n.MessagesUtil;
37  import info.magnolia.ui.api.overlay.ConfirmationCallback;
38  import info.magnolia.ui.framework.AdmincentralNodeTypes;
39  import info.magnolia.ui.api.shell.Shell;
40  import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
41  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
42  
43  import java.util.Iterator;
44  import java.util.Map;
45  import java.util.SortedSet;
46  import java.util.TreeSet;
47  
48  import org.apache.commons.lang.StringUtils;
49  
50  import com.vaadin.event.FieldEvents.BlurEvent;
51  import com.vaadin.event.FieldEvents.BlurListener;
52  import com.vaadin.event.FieldEvents.FocusEvent;
53  import com.vaadin.event.FieldEvents.FocusListener;
54  import com.vaadin.event.LayoutEvents.LayoutClickEvent;
55  import com.vaadin.event.LayoutEvents.LayoutClickListener;
56  import com.vaadin.event.ShortcutListener;
57  import com.vaadin.ui.Button.ClickEvent;
58  import com.vaadin.ui.Button.ClickListener;
59  import com.vaadin.ui.Component;
60  import com.vaadin.ui.CssLayout;
61  import com.vaadin.ui.NativeButton;
62  import com.vaadin.ui.TextField;
63  
64  /**
65   * Favorite group.
66   */
67  public final class FavoritesGroup extends CssLayout {
68  
69      private TextField titleField;
70      private NativeButton editButton;
71      private NativeButton removeButton;
72      private String title;
73      private String relPath;
74      private boolean editable;
75      private boolean selected;
76      private CssLayout wrapper;
77      private EnterKeyShortcutListener enterKeyShortcutListener;
78      private EscapeKeyShortcutListener escapeKeyShortcutListener;
79      private Shell shell;
80  
81      /**
82       * Creates an empty placeholder group.
83       */
84      public FavoritesGroup() {
85          addStyleName("no-group");
86      }
87  
88      public FavoritesGroup(final AbstractJcrNodeAdapter favoritesGroup, final FavoritesView.Listener listener, final Shell shell) {
89          this.shell = shell;
90  
91          addStyleName("favorites-group");
92          construct(favoritesGroup, listener);
93  
94          final Map<String, AbstractJcrNodeAdapter> nodeAdapters = favoritesGroup.getChildren();
95          final SortedSet<String> keys = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
96          keys.addAll(nodeAdapters.keySet());
97  
98          for (String key : keys) {
99              final AbstractJcrNodeAdapter fav = nodeAdapters.get(key);
100             final FavoritesEntry favEntry = new FavoritesEntry(fav, listener, shell);
101             addComponent(favEntry);
102         }
103     }
104 
105     /**
106      * Sets this group and all of its fav entries (if any) as unselected and non editable, that is at their initial state.
107      */
108     public void reset() {
109         // skip it if this group is a placeholder for no group fav entries, as it has no title
110         if (titleField != null) {
111             setEditable(false);
112             setSelected(false);
113         }
114         Iterator<Component> components = getComponentIterator();
115         while (components.hasNext()) {
116             Component component = components.next();
117             if(component instanceof FavoritesEntry) {
118                 FavoritesEntry fav = (FavoritesEntry) component;
119                 fav.reset();
120             }
121         }
122     }
123 
124     private void setEditable(boolean editable) {
125         this.editable = editable;
126         String icon = "icon-tick";
127         if (editable) {
128             titleField.addStyleName("editable");
129             titleField.focus();
130             titleField.selectAll();
131         } else {
132             icon = "icon-edit";
133             // discard pending changes
134             titleField.setValue(title);
135             titleField.removeStyleName("editable");
136         }
137         titleField.setReadOnly(!editable);
138         editButton.setCaption("<span class=\"" + icon + "\"></span>");
139     }
140 
141     private void setSelected(boolean selected) {
142         this.selected = selected;
143         if (selected) {
144             wrapper.addStyleName("selected");
145         } else {
146             wrapper.removeStyleName("selected");
147         }
148         titleField.setReadOnly(true);
149         editButton.setVisible(selected);
150         editButton.setCaption("<span class=\"icon-edit\"></span>");
151         removeButton.setVisible(selected);
152     }
153 
154     private void construct(final AbstractJcrNodeAdapter favoritesGroup, final FavoritesView.Listener listener) {
155         wrapper = new CssLayout();
156         wrapper.addStyleName("favorites-group-title");
157 
158         this.enterKeyShortcutListener = new EnterKeyShortcutListener(listener);
159         this.escapeKeyShortcutListener = new EscapeKeyShortcutListener();
160 
161         this.relPath = favoritesGroup.getNodeName();
162         this.title = favoritesGroup.getItemProperty(AdmincentralNodeTypes.Favorite.TITLE).getValue().toString();
163 
164         titleField = new TextField();
165         titleField.setValue(title);
166         titleField.setReadOnly(true);
167         titleField.addFocusListener(new FocusListener() {
168 
169             @Override
170             public void focus(FocusEvent event) {
171                 titleField.addShortcutListener(enterKeyShortcutListener);
172                 titleField.addShortcutListener(escapeKeyShortcutListener);
173             }
174         });
175 
176         titleField.addBlurListener(new BlurListener() {
177 
178             @Override
179             public void blur(BlurEvent event) {
180                 titleField.removeShortcutListener(enterKeyShortcutListener);
181                 titleField.removeShortcutListener(escapeKeyShortcutListener);
182             }
183         });
184 
185         wrapper.addComponent(titleField);
186 
187         editButton = new NativeButton();
188         editButton.setHtmlContentAllowed(true);
189         editButton.setCaption("<span class=\"icon-edit\"></span>");
190         editButton.addStyleName("favorite-action");
191         editButton.addClickListener(new ClickListener() {
192 
193             @Override
194             public void buttonClick(ClickEvent event) {
195                 if (selected && !editable) {
196                     setEditable(true);
197                     return;
198                 }
199                 doEditTitle(listener);
200             }
201         });
202         editButton.setVisible(false);
203         wrapper.addComponent(editButton);
204 
205         removeButton = new NativeButton();
206         removeButton.setHtmlContentAllowed(true);
207         removeButton.setCaption("<span class=\"icon-trash\"></span>");
208         removeButton.addStyleName("favorite-action");
209         removeButton.addClickListener(new ClickListener() {
210 
211             @Override
212             public void buttonClick(ClickEvent event) {
213                 shell.openConfirmation(MessageStyleTypeEnum.WARNING, MessagesUtil.get("favorites.group.confirmation.title"), MessagesUtil.get("confirmation.cannot.undo"), MessagesUtil.get("confirmation.delete.yes"), MessagesUtil.get("confirmation.no"), true, new ConfirmationCallback() {
214 
215                     @Override
216                     public void onSuccess() {
217                         listener.removeGroup(relPath);
218                     }
219 
220                     @Override
221                     public void onCancel() {
222                         // no op
223                     }
224                 });
225             }
226         });
227         removeButton.setVisible(false);
228         wrapper.addComponent(removeButton);
229 
230         addLayoutClickListener(new LayoutClickListener() {
231 
232             @Override
233             public void layoutClick(LayoutClickEvent event) {
234 
235                 if (event.getClickedComponent() == titleField) {
236                     if (!editable) {
237                         setSelected(!selected);
238                     }
239                 }
240             }
241         });
242 
243         addComponent(wrapper);
244     }
245 
246     private void doEditTitle(final FavoritesView.Listener listener) {
247         if (StringUtils.isBlank(titleField.getValue())) {
248             shell.openNotification(MessageStyleTypeEnum.ERROR, true, MessagesUtil.get("favorites.title.required"));
249             return;
250         }
251         boolean titleHasChanged = !title.equals(titleField.getValue());
252         if (editable && titleHasChanged) {
253             listener.editGroup(relPath, titleField.getValue());
254         }
255         setEditable(false);
256     }
257 
258     private class EnterKeyShortcutListener extends ShortcutListener {
259         private FavoritesView.Listener listener;
260 
261         public EnterKeyShortcutListener(final FavoritesView.Listener listener) {
262             super("", KeyCode.ENTER, null);
263             this.listener = listener;
264         }
265 
266         @Override
267         public void handleAction(Object sender, Object target) {
268             if (editable) {
269                 doEditTitle(listener);
270             } else {
271                 setEditable(true);
272             }
273         }
274     }
275 
276     private class EscapeKeyShortcutListener extends ShortcutListener {
277 
278         public EscapeKeyShortcutListener() {
279             super("", KeyCode.ESCAPE, null);
280         }
281 
282         @Override
283         public void handleAction(Object sender, Object target) {
284             reset();
285         }
286     }
287 }