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.form.field;
35  
36  import com.vaadin.data.Item;
37  import com.vaadin.data.Property;
38  import com.vaadin.data.util.converter.Converter.ConversionException;
39  import com.vaadin.server.ErrorMessage;
40  import com.vaadin.server.UserError;
41  import com.vaadin.ui.Alignment;
42  import com.vaadin.ui.Button;
43  import com.vaadin.ui.Button.ClickEvent;
44  import com.vaadin.ui.Component;
45  import com.vaadin.ui.CustomField;
46  import com.vaadin.ui.HorizontalLayout;
47  import com.vaadin.ui.NativeButton;
48  import com.vaadin.ui.TextField;
49  import com.vaadin.ui.VerticalLayout;
50  import info.magnolia.objectfactory.ComponentProvider;
51  import info.magnolia.objectfactory.Components;
52  import info.magnolia.ui.api.app.AppController;
53  import info.magnolia.ui.api.app.ChooseDialogCallback;
54  import info.magnolia.ui.api.context.UiContext;
55  import info.magnolia.ui.form.field.component.ContentPreviewComponent;
56  import info.magnolia.ui.form.field.converter.IdentifierToPathConverter;
57  import info.magnolia.ui.form.field.definition.LinkFieldDefinition;
58  import info.magnolia.ui.vaadin.integration.NullItem;
59  import info.magnolia.ui.vaadin.integration.jcr.JcrItemAdapter;
60  import org.apache.commons.lang.StringUtils;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  import javax.jcr.Node;
65  import javax.jcr.RepositoryException;
66  
67  /**
68   * A base custom field comprising a text field and a button placed to its immediate right.
69   * A {@link PropertyTranslator} can be set in order to have a different display and property stored.
70   * For example, display can be the Item path and value stored is the identifier of the Item.
71   */
72  public class LinkField extends CustomField<String> {
73      private static final Logger log = LoggerFactory.getLogger(LinkField.class);
74  
75      /**
76       * Normal {@link TextField} only exposing the fireValueChange(... that is by default protected.
77       */
78      private final class LinkFieldTextBox extends TextField {
79          @Override
80          public void fireValueChange(boolean repaintIsNotNeeded) {
81              super.fireValueChange(repaintIsNotNeeded);
82          }
83      }
84  
85      // Define layout and component
86      private final VerticalLayout rootLayout = new VerticalLayout();
87      private final HorizontalLayout linkLayout = new HorizontalLayout();
88      private final LinkFieldTextBox textField = new LinkFieldTextBox();
89      private final Button selectButton = new NativeButton();
90  
91      private final IdentifierToPathConverter converter;
92      private final LinkFieldDefinition definition;
93      private String buttonCaptionNew;
94      private String buttonCaptionOther;
95  
96      private final AppController appController;
97      private final UiContext uiContext;
98      private final ComponentProvider componentProvider;
99  
100     public LinkField(LinkFieldDefinition linkFieldDefinition, AppController appController, UiContext uiContext, ComponentProvider componentProvider) {
101         this.definition = linkFieldDefinition;
102         this.converter = definition.getIdentifierToPathConverter();
103         if (this.converter != null) {
104             this.converter.setWorkspaceName(definition.getTargetWorkspace());
105         }
106         this.appController = appController;
107         this.uiContext = uiContext;
108         this.componentProvider = componentProvider;
109         setImmediate(true);
110     }
111 
112     @Override
113     protected Component initContent() {
114         addStyleName("linkfield");
115         // Initialize root
116         rootLayout.setSizeFull();
117         rootLayout.setSpacing(true);
118         // Handle Text Field
119         textField.setImmediate(true);
120         textField.setWidth(100, Unit.PERCENTAGE);
121         textField.setNullRepresentation("");
122         textField.setNullSettingAllowed(true);
123         // Handle Link Layout (Text Field & Select Button)
124         linkLayout.setSizeFull();
125         linkLayout.addComponent(textField);
126         linkLayout.setExpandRatio(textField, 1);
127         linkLayout.setComponentAlignment(textField, Alignment.MIDDLE_LEFT);
128         // Only Handle Select button if the Text field is not Read Only.
129         if (!textField.isReadOnly()) {
130             selectButton.addStyleName("magnoliabutton");
131             linkLayout.addComponent(selectButton);
132             linkLayout.setExpandRatio(selectButton, 0);
133             linkLayout.setComponentAlignment(selectButton, Alignment.MIDDLE_RIGHT);
134         }
135         selectButton.addClickListener(createButtonClickListener());
136         setButtonCaption(StringUtils.EMPTY);
137         rootLayout.addComponent(linkLayout);
138 
139         // Register the content preview if it's define.
140         if (definition.getContentPreviewDefinition() != null && definition.getContentPreviewDefinition().getContentPreviewClass() != null) {
141             registerContentPreview();
142         }
143         return rootLayout;
144     }
145 
146     public TextField getTextField() {
147         return this.textField;
148     }
149 
150     public Button getSelectButton() {
151         return this.selectButton;
152     }
153 
154     @Override
155     public String getValue() {
156         return textField.getValue();
157     }
158 
159     @Override
160     public void setValue(String newValue) throws ReadOnlyException, ConversionException {
161         textField.setValue(newValue);
162     }
163 
164     /**
165      * Update the Link component. <br>
166      * - Set text Field as read only if desired. In this case remove the add button.
167      * - If it is not read only. update the button label.
168      */
169     private void updateComponents(String currentValue) {
170         if (!definition.isFieldEditable() && StringUtils.isNotBlank(currentValue)) {
171             textField.setReadOnly(true);
172             if (linkLayout.getComponentIndex(selectButton) != -1) {
173                 linkLayout.removeComponent(selectButton);
174             }
175         } else {
176             setButtonCaption(currentValue);
177         }
178     }
179 
180     /**
181      * Set propertyDatasource.
182      * If the translator is not null, set it as datasource.
183      */
184     @Override
185     @SuppressWarnings("rawtypes")
186     public void setPropertyDataSource(Property newDataSource) {
187         if (converter != null) {
188             textField.setConverter(converter);
189         }
190         textField.setPropertyDataSource(newDataSource);
191         textField.addValueChangeListener(new ValueChangeListener() {
192             @Override
193             public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
194                 String value = event.getProperty().getValue() != null ? event.getProperty().getValue().toString() : StringUtils.EMPTY;
195                 updateComponents(value);
196             }
197         });
198 
199         super.setPropertyDataSource(newDataSource);
200     }
201 
202     @Override
203     @SuppressWarnings("rawtypes")
204     public Property getPropertyDataSource() {
205         return textField.getPropertyDataSource();
206     }
207 
208     @Override
209     public Class<String> getType() {
210         return String.class;
211     }
212 
213     private void setButtonCaption(String value) {
214         if (StringUtils.isNotBlank(value)) {
215             selectButton.setCaption(buttonCaptionOther);
216             selectButton.setDescription(buttonCaptionOther);
217         } else {
218             selectButton.setCaption(buttonCaptionNew);
219             selectButton.setDescription(buttonCaptionNew);
220         }
221     }
222 
223     private void registerContentPreview() {
224         final ContentPreviewComponent<?> contentPreviewComponent = Components.newInstance(definition.getContentPreviewDefinition().getContentPreviewClass(), definition.getTargetWorkspace(), componentProvider);
225         textField.addValueChangeListener(new ValueChangeListener() {
226             @Override
227             public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
228                 String itemReference = event.getProperty().getValue().toString();
229                 contentPreviewComponent.onValueChange(itemReference);
230             }
231         });
232         rootLayout.addComponentAsFirst(contentPreviewComponent);
233         if (StringUtils.isNotBlank(textField.getValue())) {
234             textField.fireValueChange(false);
235         }
236     }
237 
238     /**
239      * Create the Button click Listener. On click: Create a Dialog and
240      * Initialize callback handling.
241      */
242     private Button.ClickListener createButtonClickListener() {
243         return new Button.ClickListener() {
244             @Override
245             public void buttonClick(ClickEvent event) {
246 
247                 appController.openChooseDialog(definition.getAppName(), uiContext, textField.getValue(), new ChooseDialogCallback() {
248                     @Override
249                     public void onItemChosen(String actionName, final Item chosenValue) {
250                         String propertyName = definition.getTargetPropertyToPopulate();
251                         String newValue = null;
252                         if (chosenValue != null && !(chosenValue instanceof NullItem)) {
253                             javax.jcr.Item jcrItem = ((JcrItemAdapter) chosenValue).getJcrItem();
254                             if (jcrItem.isNode()) {
255                                 final Node selected = (Node) jcrItem;
256                                 try {
257                                     boolean isPropertyExisting = StringUtils.isNotBlank(propertyName) && selected.hasProperty(propertyName);
258                                     newValue = isPropertyExisting ? selected.getProperty(propertyName).getString() : selected.getPath();
259                                 } catch (RepositoryException e) {
260                                     log.error("Not able to access the configured property. Value will not be set.", e);
261                                 }
262                             }
263                         }
264                         setValue(newValue);
265                     }
266 
267                     @Override
268                     public void onCancel() {
269                     }
270                 });
271             }
272         };
273     }
274 
275     /**
276      * Caption section.
277      */
278     public void setButtonCaptionNew(String buttonCaptionNew) {
279         this.buttonCaptionNew = buttonCaptionNew;
280     }
281 
282     public void setButtonCaptionOther(String buttonCaptionOther) {
283         this.buttonCaptionOther = buttonCaptionOther;
284     }
285 
286     @Override
287     public boolean isValid() {
288         if (this.isRequired() && StringUtils.isBlank(getValue())) {
289             return false;
290         } else {
291             return true;
292         }
293     }
294 
295 
296     @Override
297     public ErrorMessage getErrorMessage() {
298         if (this.isRequired() && StringUtils.isBlank(getValue())) {
299             return new UserError(getRequiredError());
300         } else {
301             return null;
302         }
303     }
304 
305 }