View Javadoc
1   /**
2    * This file Copyright (c) 2012-2017 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.dialog.formdialog;
35  
36  import info.magnolia.cms.i18n.I18nContentSupport;
37  import info.magnolia.objectfactory.ComponentProvider;
38  import info.magnolia.ui.api.app.SubAppContext;
39  import info.magnolia.ui.api.context.UiContext;
40  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
41  import info.magnolia.ui.api.view.View;
42  import info.magnolia.ui.form.Form;
43  import info.magnolia.ui.form.FormItem;
44  import info.magnolia.ui.form.FormTab;
45  import info.magnolia.ui.form.definition.FormDefinition;
46  import info.magnolia.ui.form.definition.TabDefinition;
47  import info.magnolia.ui.form.field.definition.CompositeFieldDefinition;
48  import info.magnolia.ui.form.field.definition.ConfiguredFieldDefinition;
49  import info.magnolia.ui.form.field.definition.FieldDefinition;
50  import info.magnolia.ui.form.field.definition.MultiValueFieldDefinition;
51  import info.magnolia.ui.form.field.factory.FieldFactory;
52  import info.magnolia.ui.form.field.factory.FieldFactoryFactory;
53  import info.magnolia.ui.vaadin.form.FormViewReduced;
54  import info.magnolia.ui.vaadin.richtext.TextAreaStretcher;
55  
56  import java.util.Iterator;
57  import java.util.List;
58  import java.util.Locale;
59  
60  import javax.inject.Inject;
61  
62  import org.apache.commons.lang3.StringUtils;
63  import org.vaadin.aceeditor.AceEditor;
64  import org.vaadin.openesignforms.ckeditor.CKEditorTextField;
65  
66  import com.vaadin.data.Item;
67  import com.vaadin.ui.AbstractComponent;
68  import com.vaadin.ui.AbstractField;
69  import com.vaadin.ui.Component;
70  import com.vaadin.ui.CssLayout;
71  import com.vaadin.ui.Field;
72  import com.vaadin.ui.TextArea;
73  
74  /**
75   * Builder for forms.
76   */
77  public class FormBuilder {
78  
79      private final FieldFactoryFactory fieldFactoryFactory;
80      private final I18NAuthoringSupport i18nAuthoringSupport;
81      private final UiContext uiContext;
82      private final ComponentProvider componentProvider;
83  
84      @Inject
85      public FormBuilder(FieldFactoryFactory fieldFactoryFactory, I18NAuthoringSupport i18nAuthoringSupport, UiContext uiContext, ComponentProvider componentProvider) {
86          this.fieldFactoryFactory = fieldFactoryFactory;
87          this.i18nAuthoringSupport = i18nAuthoringSupport;
88          this.uiContext = uiContext;
89          this.componentProvider = componentProvider;
90      }
91  
92      /**
93       * @deprecated since 5.3.9 - use {@link #FormBuilder(FieldFactoryFactory, I18NAuthoringSupport, UiContext, ComponentProvider)} instead.
94       */
95      @Deprecated
96      public FormBuilder(FieldFactoryFactory fieldFactoryFactory, I18NAuthoringSupport i18nAuthoringSupport, ComponentProvider componentProvider) {
97          this(fieldFactoryFactory, i18nAuthoringSupport, componentProvider.getComponent(UiContext.class), componentProvider);
98      }
99  
100     /**
101      * @deprecated since 5.3.4 - use {@link #FormBuilder(FieldFactoryFactory, I18NAuthoringSupport, UiContext, ComponentProvider)} instead.
102      */
103     @Deprecated
104     public FormBuilder(FieldFactoryFactory fieldFactoryFactory, I18nContentSupport i18nContentSupport, I18NAuthoringSupport i18nAuthoringSupport, ComponentProvider componentProvider) {
105         this(fieldFactoryFactory, i18nAuthoringSupport, componentProvider.getComponent(UiContext.class), componentProvider);
106     }
107 
108     /**
109      * return FormView populated with values from FormDefinition and Item.
110      */
111     public void buildForm(FormView view, FormDefinition formDefinition, Item item, FormItem parent) {
112         final String description = formDefinition.getDescription();
113         final String label = formDefinition.getLabel();
114 
115         // If we remove the if blocks below, we show up the (first) generated key for this label/description (unless it is translated),
116         // thus overriding the dialog's title. See MGNLUI-2207.
117         // The 'container' of the form (ie a dialog) may already have set these values on the view based on its definition (dialogDefintion).
118         // Only if form specifies values - then use forms values.
119         if (StringUtils.isNotBlank(description) && !isMessageKey(description)) {
120             view.setDescription(description);
121         }
122 
123         if (StringUtils.isNotBlank(label) && !isMessageKey(label)) {
124             view.setCaption(label);
125         }
126 
127         buildReducedForm(formDefinition, view, item, parent);
128 
129         if (hasI18nAwareFields(formDefinition)) {
130             List<Locale> locales = i18nAuthoringSupport.getAvailableLocales(item);
131             view.setAvailableLocales(locales);
132             // As of 5.3.9 only subapp context supports tracking current authoring locale, we may expand that to other UiContexts in the future if needed.
133             if (uiContext instanceof SubAppContext && ((SubAppContext) uiContext).getAuthoringLocale() != null) {
134                 view.setCurrentLocale(((SubAppContext) uiContext).getAuthoringLocale());
135             } else {
136                 view.setCurrentLocale(i18nAuthoringSupport.getDefaultLocale(item));
137             }
138         }
139     }
140 
141     public View buildView(FormDefinition formDefinition, Item item) {
142 
143         final CssLayout view = new CssLayout();
144         view.setSizeFull();
145 
146         for (TabDefinition tabDefinition : formDefinition.getTabs()) {
147             List<FieldDefinition> fields = tabDefinition.getFields();
148             if (fields.size() == 0) { // skip empty tabs
149                 continue;
150             }
151             for (final FieldDefinition fieldDefinition : fields) {
152                 final FieldFactory formField = fieldFactoryFactory.createFieldFactory(fieldDefinition, item);
153                 if (formField == null) {
154                     continue;
155                 }
156                 formField.setComponentProvider(componentProvider);
157 
158                 final View fieldView = formField.getView();
159 
160                 view.addComponent(fieldView.asVaadinComponent());
161 
162             }
163         }
164         return new View() {
165             @Override
166             public Component asVaadinComponent() {
167                 return view;
168             }
169         };
170     }
171 
172     public void buildReducedForm(FormDefinition formDefinition, FormViewReduced view, Item item, FormItem parent) {
173         final Form form = new Form(formDefinition);
174         form.setParent(parent);
175         view.setItemDataSource(item);
176 
177         boolean firstFieldIsBuilt = false;
178 
179         for (TabDefinition tabDefinition : formDefinition.getTabs()) {
180             FormTab tab = buildFormTab(tabDefinition, item, form);
181             if (tab == null) continue;
182 
183             Iterator<Component> fieldIt = tab.getContainer().iterator();
184             while (fieldIt.hasNext()) {
185                 view.addField((Field<?>) fieldIt.next());
186             }
187 
188             fieldIt = tab.getContainer().iterator();
189             if (!firstFieldIsBuilt && fieldIt.hasNext()) {
190                 ((Component.Focusable) fieldIt.next()).focus();
191                 firstFieldIsBuilt = true;
192             }
193 
194             view.addFormSection(tabDefinition.getLabel(), tab.getContainer());
195         }
196         view.setShowAllEnabled(view.getFormSections().size() > 1);
197     }
198 
199     public FormTab buildFormTab(TabDefinition tabDefinition, Item itemDatasource, Form parentForm) {
200         List<FieldDefinition> fields = tabDefinition.getFields();
201         if (fields == null || fields.isEmpty()) { // skip empty tabs
202             return null;
203         }
204         FormTab tab = new FormTab(tabDefinition);
205         tab.setParent(parentForm);
206         tab.getContainer().setName(tabDefinition.getName());
207         for (final FieldDefinition fieldDefinition : fields) {
208             final Field<?> field = createField(fieldDefinition, itemDatasource, tab);
209             if (field != null) {
210                 tab.addField(field);
211                 final String helpDescription = fieldDefinition.getDescription();
212                 if (StringUtils.isNotBlank(helpDescription) && !isMessageKey(helpDescription)) {
213                     tab.setComponentHelpDescription(field, helpDescription);
214                 }
215             }
216         }
217         return tab;
218     }
219 
220     public Field<?> createField(FieldDefinition fieldDefinition, Item itemDatasource, FormTab parentTab) {
221         final FieldFactory fieldFactory = fieldFactoryFactory.createFieldFactory(fieldDefinition, itemDatasource);
222         if (fieldFactory == null) {
223             return null;
224         }
225 
226         fieldFactory.setComponentProvider(componentProvider);
227         if (parentTab != null) {
228             fieldFactory.setParent(parentTab);
229         }
230 
231         final Field<?> field = fieldFactory.createField();
232         if (field instanceof AbstractComponent) {
233             ((AbstractComponent) field).setImmediate(true);
234         }
235 
236         // Hide validation for all fields by default; it will be shown by the Form upon validation (if invalid)
237         if (field instanceof AbstractField) {
238             ((AbstractField) field).setValidationVisible(false);
239         }
240 
241         if (field instanceof TextArea || field instanceof CKEditorTextField || field instanceof AceEditor) {
242             TextAreaStretcher.extend(field);
243         }
244 
245         return field;
246     }
247 
248     private boolean hasI18nAwareFields(FormDefinition formDefinition) {
249 
250         for (TabDefinition tab : formDefinition.getTabs()) {
251             for (FieldDefinition field : tab.getFields()) {
252                 if (isI18nAware(field)) {
253                     return true;
254                 }
255             }
256         }
257         return false;
258     }
259 
260     private boolean isI18nAware(FieldDefinition field) {
261         if (field.isI18n()) {
262             return true;
263         }
264         if (field instanceof CompositeFieldDefinition) {
265             for (ConfiguredFieldDefinition configuredFieldDefinition : ((CompositeFieldDefinition) field).getFields()) {
266                 if (isI18nAware(configuredFieldDefinition)) {
267                     return true;
268                 }
269             }
270         } else if (field instanceof MultiValueFieldDefinition) {
271             field = ((MultiValueFieldDefinition) field).getField();
272             return isI18nAware(field);
273         }
274         return false;
275     }
276 
277     /**
278      * @deprecated is a hack and should not be used. See MGNLUI-2207.
279      */
280     @Deprecated
281     private boolean isMessageKey(final String text) {
282         return !text.contains(" ") && text.contains(".") && !text.endsWith(".");
283     }
284 }