View Javadoc
1   /**
2    * This file Copyright (c) 2014 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.workbench.tree;
35  
36  import java.util.Locale;
37  
38  import com.vaadin.data.Container;
39  import com.vaadin.data.Property;
40  import com.vaadin.data.Property.ValueChangeListener;
41  import com.vaadin.data.util.converter.AbstractStringToNumberConverter;
42  import com.vaadin.event.FieldEvents.BlurListener;
43  import com.vaadin.ui.AbstractTextField;
44  import com.vaadin.ui.Component;
45  import com.vaadin.ui.DefaultFieldFactory;
46  import com.vaadin.ui.Field;
47  import com.vaadin.ui.TableFieldFactory;
48  import com.vaadin.ui.TextField;
49  
50  /**
51   * The InplaceEditingFieldFactory is responsible for creating input fields in table cells for inplace-editing.
52   * Compared to a standard Vaadin {@link TableFieldFactory}, this one only creates an input field in one cell at a time,
53   * i.e. corresponding to a single combination of itemId and container's propertyId.<br />
54   * <br/>
55   * It also expects a field {@link BlurListener} to be set as a hook to persist changes.
56   */
57  public class InplaceEditingFieldFactory implements TableFieldFactory {
58  
59      private Object editingItemId;
60      private Object editingPropertyId;
61  
62      private Field<?> field;
63      private BlurListener fieldBlurListener;
64  
65      /**
66       * @return the id of the item currently being edited
67       */
68      public Object getEditingItemId() {
69          return editingItemId;
70      }
71  
72      /**
73       * @return the id of the item's property currently being edited
74       */
75      public Object getEditingPropertyId() {
76          return editingPropertyId;
77      }
78  
79      public Field<?> getField() {
80          return field;
81      }
82  
83      /**
84       * Sets the item and property to edit.
85       */
86      public void setEditing(Object editingItemId, Object editingPropertyId) {
87          this.editingItemId = editingItemId;
88          this.editingPropertyId = editingPropertyId;
89          if (editingItemId == null || editingPropertyId == null) {
90              field = null;
91          }
92      }
93  
94      /**
95       * Sets the blur listener that should react when leaving the inplace-editing field.
96       */
97      public void setFieldBlurListener(BlurListener fieldBlurListener) {
98          this.fieldBlurListener = fieldBlurListener;
99      }
100 
101     @Override
102     public Field<?> createField(Container container, Object itemId, Object propertyId, Component uiContext) {
103         // add field only for selected row/column.
104         if (itemId.equals(editingItemId) && propertyId.equals(editingPropertyId)) {
105             Field<?> field = createFieldAndRegister(container, itemId, propertyId);
106             // register component on the table (only for partial updates)
107             // uiContext.registerComponent(field);
108             this.field = field;
109             return field;
110         }
111         return null;
112     }
113 
114     /**
115      * For partial updates to work, we need to perform a dry run to attach the component to the table beforehand,
116      * i.e. before it is actually requested at paint phase by the table.
117      */
118     private Field<?> createFieldAndRegister(Container container, Object itemId, Object propertyId) {
119 
120         Property<?> containerProperty = container.getContainerProperty(itemId, propertyId);
121         // the previous call can return null, i.e. when clicking on an empty cell of a node row (i.e. /config/server and then the "value" cell)
122         if (containerProperty == null) {
123             return null;
124         }
125 
126         Class<?> type = containerProperty.getType();
127         Field<?> field = createFieldByPropertyType(type);
128         if (field != null) {
129             field.setCaption(DefaultFieldFactory.createCaptionByPropertyId(propertyId));
130             field.setSizeFull();
131         }
132 
133         // set TextField listeners
134         if (field instanceof AbstractTextField) {
135             final AbstractTextField tf = (AbstractTextField) field;
136             tf.addBlurListener(fieldBlurListener);
137             tf.focus();
138 
139             tf.addValueChangeListener(new ValueChangeListener() {
140 
141                 @Override
142                 public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
143                     final Object text = event.getProperty().getValue();
144                     if (text instanceof String) {
145                         tf.selectAll();
146                     }
147                     tf.removeValueChangeListener(this);
148                 }
149             });
150         }
151         return field;
152     }
153 
154     private Field<?> createFieldByPropertyType(Class<?> type) {
155         if (type == null) {
156             return null;
157         }
158         Field<?> field = new TextField();
159         // FIXME MGNLUI-1855 To remove once Vaadin 7.2 will be used. Currently we need to assign converter for properties with type Long because otherwise Vaadin assigns incompatible StringToNumberConverter.
160         if (Long.class.equals(type)) {
161             ((AbstractTextField) field).setConverter(new StringToLongConverter());
162         }
163         return field;
164     }
165 
166     /**
167      * The StringToLongConverter.<br>
168      * MGNLUI-1855 This should be handled by vaadin, but StringToNumberConverter throws conversion exception when used
169      * with a Long property in Vaadin 7.1. This should be fixed, unfortunately not before 7.2, so we need that converter
170      * for the time being.<br>
171      * As a result, this class will have a short life span, this is why it is kept private and deprecated.
172      */
173     @Deprecated
174     static class StringToLongConverter extends AbstractStringToNumberConverter<Long> {
175         // FIXME MGNLUI-1855 To remove once Vaadin 7.2 will be used.
176         @Override
177         public Long convertToModel(String value, Class<? extends Long> targetType, Locale locale) throws ConversionException {
178             Number n = convertToNumber(value, targetType, locale);
179             return n == null ? null : n.longValue();
180         }
181 
182         @Override
183         public Class<Long> getModelType() {
184             return Long.class;
185         }
186     }
187 
188 }