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