View Javadoc
1   /**
2    * This file Copyright (c) 2015-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.objectfactory.Components;
37  import info.magnolia.ui.api.app.SubAppContext;
38  import info.magnolia.ui.api.context.UiContext;
39  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
40  import info.magnolia.ui.form.Form;
41  import info.magnolia.ui.form.FormItem;
42  import info.magnolia.ui.form.FormPresenter;
43  import info.magnolia.ui.form.FormTab;
44  import info.magnolia.ui.form.definition.FormDefinition;
45  import info.magnolia.ui.form.definition.TabDefinition;
46  import info.magnolia.ui.form.field.definition.FieldDefinition;
47  import info.magnolia.ui.vaadin.form.FormSection;
48  import info.magnolia.ui.vaadin.form.FormViewReduced;
49  
50  import java.util.Iterator;
51  import java.util.List;
52  import java.util.Locale;
53  import java.util.Map;
54  import java.util.Optional;
55  
56  import javax.inject.Inject;
57  
58  import org.apache.commons.lang3.ObjectUtils;
59  import org.apache.commons.lang3.StringUtils;
60  
61  import com.google.common.collect.Maps;
62  import com.vaadin.ui.Component;
63  import com.vaadin.v7.data.Item;
64  import com.vaadin.v7.ui.Field;
65  
66  /**
67   * Default implementation of {@link info.magnolia.ui.form.FormPresenter}. Main responsibilities of the class are:
68   * <ul>
69   * <li>Communication with {@link FormBuilder}</li>
70   * <li>Tracking locale-specific representations of {@link FormSection}s</li>
71   * <li>Listening to the {@link FormView}'s locale switcher and setting {@link SubAppContext}-wide locale.</li>
72   * </ul>
73   *
74   * <strong>NOTE:</strong> ideally this class should have reside at the {@code info.magnolia.ui.form} package in <strong>magnolia-ui-form</strong>,
75   * but due to {@link FormBuilder} being in <strong>magnolia-ui-dialog</strong> and the incorrect relation between {@link FormView}, {@link info.magnolia.ui.dialog.DialogView DialogView} and
76   * {@link FormViewReduced} it has to in the current package.
77   */
78  public class FormPresenterImpl implements FormPresenter {
79  
80      private final Map<String, FormTab> cachedTabs = Maps.newHashMap();
81  
82      private final I18NAuthoringSupport i18nAuthoringSupport;
83  
84      private final FormBuilder formBuilder;
85  
86      private final UiContext uiContext;
87  
88      private FormDefinition formDefinition;
89  
90      private FormViewReduced formView;
91  
92      private Locale activeLocale;
93  
94      private Item itemDatasource;
95  
96      @Inject
97      public FormPresenterImpl(FormBuilder formBuilder, UiContext uiContext, I18NAuthoringSupport i18nAuthoringSupport) {
98          this.i18nAuthoringSupport = i18nAuthoringSupport;
99          this.formBuilder = formBuilder;
100         this.uiContext = uiContext;
101     }
102 
103     /**
104      * @deprecated since 5.4.12 - use {@link #FormPresenterImpl(FormBuilder, UiContext, I18NAuthoringSupport)} instead.
105      */
106     @Deprecated
107     public FormPresenterImpl(FormBuilder formBuilder, UiContext uiContext) {
108         this(formBuilder, uiContext, Components.getComponent(I18NAuthoringSupport.class));
109     }
110 
111     @Override
112     public void presentView(FormViewReduced formView, FormDefinition formDefinition, Item item, FormItem parent) {
113         this.formView = formView;
114         this.itemDatasource = item;
115         this.formDefinition = formDefinition;
116 
117         cachedTabs.clear();
118         activeLocale = getCurrentAuthoringLocale(uiContext, item);
119         // FormBuilder still expects the FormView object to build, so we have to cast here but ideally that should be refactored
120         buildForm((FormView) this.formView, formDefinition, item, parent, activeLocale);
121 
122         // We should expand locale-awareness onto all the UI contexts.
123         if (uiContext instanceof SubAppContext) {
124             formView.setListener(newLocale -> {
125                 if (newLocale != null && !ObjectUtils.equals(((SubAppContext) uiContext).getAuthoringLocale(), newLocale)) {
126                     setLocale(newLocale);
127                 }
128             });
129         }
130     }
131 
132     @Override
133     public boolean isValid() {
134         return formView.isValid();
135     }
136 
137     @Override
138     public void setLocale(Locale locale) {
139         if (uiContext instanceof SubAppContext && !ObjectUtils.equals(locale, this.activeLocale)) {
140             this.activeLocale = locale;
141             ((SubAppContext) uiContext).setAuthoringLocale(locale);
142             updateForm(locale);
143         }
144     }
145 
146     protected void updateForm(Locale locale) {
147         List<FormSection> formSections = formView.getFormSections();
148         for (FormSection formSection : formSections) {
149             String tabName = formSection.getName();
150             Optional<TabDefinition> matchingTab = formDefinition.getTabs().stream()
151                     .filter(tabDefinition -> tabDefinition.getName().equals(tabName))
152                     .findFirst();
153 
154             if (matchingTab.isPresent()) {
155                 TabDefinition tabDefinition = matchingTab.get();
156                 FormTab formTab = cachedTabs.get(tabDefinition.getName());
157                 formSection.removeAllComponents();
158                 for (FieldDefinition fieldDefinition : tabDefinition.getFields()) {
159                     Field<?> field = formBuilder.createField(fieldDefinition, itemDatasource, formTab);
160                     if (field != null) {
161                         formSection.addComponent(field);
162                         final String helpDescription = fieldDefinition.getDescription();
163                         if (StringUtils.isNotBlank(helpDescription) && !formBuilder.isMessageKey(helpDescription)) {
164                             formSection.setComponentHelpDescription(field, helpDescription);
165                         }
166                     }
167                 }
168             }
169         }
170 
171         if (formView instanceof ResurfaceFormViewImpl) {
172             ((ResurfaceFormViewImpl) formView).updateFormView(formSections);
173         }
174     }
175 
176     private void buildForm(FormView view, FormDefinition formDefinition, Item item, FormItem parent, Locale locale) {
177         final String description = formDefinition.getDescription();
178         final String label = formDefinition.getLabel();
179 
180         // If we remove the if blocks below, we show up the (first) generated key for this label/description (unless it is translated),
181         // thus overriding the dialog's title. See MGNLUI-2207.
182         // The 'container' of the form (ie a dialog) may already have set these values on the view based on its definition (dialogDefinition).
183         // Only if form specifies values - then use forms values.
184         if (StringUtils.isNotBlank(description) && !formBuilder.isMessageKey(description)) {
185             view.setDescription(description);
186         }
187 
188         if (StringUtils.isNotBlank(label) && !formBuilder.isMessageKey(label)) {
189             view.setCaption(label);
190         }
191 
192         buildReducedForm(formDefinition, view, item, parent, locale);
193 
194         if (formBuilder.hasI18nAwareFields(formDefinition)) {
195             view.setAvailableLocales(i18nAuthoringSupport.getAvailableLocales(item));
196             view.setCurrentLocale(locale);
197         }
198     }
199 
200     private void buildReducedForm(FormDefinition formDefinition, FormViewReduced view, Item item, FormItem parent, Locale locale) {
201         final Form/Form.html#Form">Form form = new Form(formDefinition);
202         form.setParent(parent);
203         view.setItemDataSource(item);
204 
205         boolean firstFieldIsBuilt = false;
206 
207         for (TabDefinition tabDefinition : formDefinition.getTabs()) {
208             FormTab tab = buildFormTab(tabDefinition, item, form, locale);
209             if (tab == null) continue;
210 
211             Iterator<Component> fieldIt = tab.getContainer().iterator();
212             while (fieldIt.hasNext()) {
213                 view.addField((Field<?>) fieldIt.next());
214             }
215 
216             fieldIt = tab.getContainer().iterator();
217             if (!firstFieldIsBuilt && fieldIt.hasNext()) {
218                 ((Component.Focusable) fieldIt.next()).focus();
219                 firstFieldIsBuilt = true;
220             }
221 
222             view.addFormSection(tabDefinition.getLabel(), tab.getContainer());
223             cachedTabs.put(tabDefinition.getName(), tab);
224         }
225         view.setShowAllEnabled(view.getFormSections().size() > 1);
226     }
227 
228     private FormTab buildFormTab(TabDefinition tabDefinition, Item itemDatasource, Form parentForm, Locale locale) {
229         List<FieldDefinition> fields = tabDefinition.getFields();
230         if (fields == null || fields.isEmpty()) { // skip empty tabs
231             return null;
232         }
233         FormTabormTab.html#FormTab">FormTab tab = new FormTab(tabDefinition);
234         tab.setParent(parentForm);
235         tab.getContainer().setName(tabDefinition.getName());
236         for (final FieldDefinition fieldDefinition : fields) {
237             Field<?> field = formBuilder.createField(fieldDefinition, itemDatasource, tab);
238             if (field != null) {
239                 tab.addField(field);
240                 final String helpDescription = fieldDefinition.getDescription();
241                 if (StringUtils.isNotBlank(helpDescription) && !formBuilder.isMessageKey(helpDescription)) {
242                     tab.setComponentHelpDescription(field, helpDescription);
243                 }
244             }
245         }
246         return tab;
247     }
248 
249     /**
250      * As of 5.3.9 only sub-app context supports tracking current authoring locale, we may expand that to other UiContexts in the future if needed.
251      */
252     private Locale getCurrentAuthoringLocale(UiContext uiContext, Item item) {
253         if (uiContext instanceof SubAppContext && ((SubAppContext) uiContext).getAuthoringLocale() != null) {
254             return ((SubAppContext) uiContext).getAuthoringLocale();
255         } else {
256             return i18nAuthoringSupport.getDefaultLocale(item);
257         }
258     }
259 }