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