1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework.databinding.view;
35
36 import static java.util.stream.Collectors.toList;
37
38 import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
39 import info.magnolia.ui.field.ComplexPropertyDefinition;
40 import info.magnolia.ui.field.factory.FieldBinder;
41 import info.magnolia.ui.field.factory.FieldFactory;
42 import info.magnolia.ui.form.FormDefinition;
43 import info.magnolia.ui.framework.databinding.ItemProviderStrategy;
44 import info.magnolia.ui.framework.ioc.UiContextBoundComponentProvider;
45 import info.magnolia.ui.framework.layout.LayoutDefinition;
46 import info.magnolia.ui.framework.layout.LayoutProducer;
47 import info.magnolia.ui.vaadin.form.field.FieldLayout;
48
49 import java.util.Collections;
50 import java.util.LinkedHashMap;
51 import java.util.List;
52 import java.util.Locale;
53 import java.util.Map;
54 import java.util.Optional;
55 import java.util.stream.Stream;
56
57 import javax.inject.Inject;
58
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import com.vaadin.data.Binder;
63 import com.vaadin.data.BinderValidationStatus;
64 import com.vaadin.data.HasValue;
65 import com.vaadin.data.ValidationException;
66 import com.vaadin.ui.Component;
67
68
69
70
71
72
73
74
75 public class FormView<T> implements EditorView<T> {
76
77 private static final Logger log = LoggerFactory.getLogger(FormView.class);
78
79 private final Map<EditorView<?>, ItemProviderStrategy> subForms = new LinkedHashMap<>();
80 private final Map<Locale, Binder<T>> localisedBinders = new LinkedHashMap<>();
81 private final Map<Locale, Component> localisedLayouts = new LinkedHashMap<>();
82
83 private final I18NAuthoringSupport i18NAuthoringSupport;
84
85 @Inject
86 public FormView(FormDefinition<T> formDefinition,
87 I18NAuthoringSupport i18NAuthoringSupport) {
88 this.i18NAuthoringSupport = i18NAuthoringSupport;
89 final FormPresenter<T> formPresenter = getComponentProvider().newInstance(FormPresenter.class);
90 List<Locale> availableLocales = i18NAuthoringSupport.getAvailableLocales();
91 if (availableLocales.isEmpty()) {
92 availableLocales = Collections.singletonList(i18NAuthoringSupport.getDefaultLocale());
93 }
94
95 availableLocales
96 .forEach(locale -> createLocaleSpecificBinderAndLayout(formDefinition, formPresenter, locale));
97 }
98
99 @Override
100 public void populate(T item) {
101 localisedBinders.values().forEach(binder -> binder.readBean(item));
102 subForms.forEach(this::populateSubForm);
103 }
104
105
106
107
108
109 @Override
110 public List<BinderValidationStatus<?>> validate() {
111 final Stream<BinderValidationStatus<T>> fieldValidationResults = localisedBinders.values().stream().map(Binder::validate);
112 final Stream<BinderValidationStatus<?>> subFormValidationResults = subForms.keySet().stream()
113 .flatMap(childForm -> childForm.validate().stream());
114
115 return Stream.concat(fieldValidationResults, subFormValidationResults).collect(toList());
116 }
117
118 @Override
119 public void write(T item) {
120 localisedBinders.values().forEach(binder -> safeWrite(binder, item));
121 subForms.forEach(this::writeSubForm);
122 }
123
124 @Override
125 public Component asVaadinComponent() {
126 return getLayout(i18NAuthoringSupport.getDefaultLocale());
127 }
128
129 @Override
130 public Component getLayout(Locale locale) {
131 return localisedLayouts.get(Optional.ofNullable(locale).orElse(i18NAuthoringSupport.getDefaultLocale()));
132 }
133
134 private <FT> void populateSubForm(EditorView<FT> formView, ItemProviderStrategy<FT> itemProviderStrategy) {
135 itemProviderStrategy.read().ifPresent(formView::populate);
136 }
137
138 private <FT> void writeSubForm(EditorView<FT> subForm, ItemProviderStrategy<FT> itemProviderStrategy) {
139 itemProviderStrategy.read().ifPresent(subForm::write);
140 }
141
142 private void safeWrite(Binder<T> binder, T item) {
143 try {
144 if (binder.hasChanges()) {
145 binder.writeBean(item);
146 }
147 } catch (ValidationException e) {
148 log.error("{}", e.getMessage(), e);
149 }
150 }
151
152 private void createLocaleSpecificBinderAndLayout(FormDefinition<T> formDefinition, FormPresenter<T> formPresenter, Locale locale) {
153 Binder<T> binder = formPresenter.createBinder(formDefinition, locale);
154 localisedBinders.put(locale, binder);
155
156 log.debug("Creating layout for form named '{}' and locale {}", formDefinition.getName(), locale);
157 final Map<String, Component> components = initialiseAndGetLocaleSpecificComponents(formDefinition, locale);
158 localisedLayouts.computeIfAbsent(locale, l -> doCreateLayout(formDefinition.getLayout(), components, binder));
159 }
160
161 private Map<String, Component> initialiseAndGetLocaleSpecificComponents(FormDefinition<T> formDefinition, Locale locale) {
162 final Map<String, Component> localisedComponents = new LinkedHashMap<>();
163
164 formDefinition.getFieldDefinitions().forEach(fieldDefinition -> {
165 final String key = fieldDefinition.getName();
166 log.debug("Creating component for {} and locale {}", key, locale);
167
168 UiContextBoundComponentProvider fieldComponentProvider = getComponentProvider().inChildContext(fieldDefinition);
169
170 FieldBinder<?> fieldBinder = (FieldBinder<?>) fieldComponentProvider.newInstance(fieldDefinition.getFieldBinderClass());
171 FieldFactory<?> fieldFactory = (FieldFactory<?>) fieldComponentProvider.newInstance(fieldDefinition.getFactoryClass());
172
173 HasValue field = (HasValue) fieldFactory.createField();
174 Component component = (Component) field;
175 FieldLayout fieldLayout = FieldLayout.of(component, fieldDefinition.getDescription());
176
177 Binder.BindingBuilder initialBinding = localisedBinders.get(locale)
178 .forField(field)
179 .withValidationStatusHandler(status -> fieldLayout.getValidationStatusHandler().accept(status.getMessage().orElse("")));
180
181 fieldBinder
182 .configureBinding(fieldDefinition, initialBinding)
183 .bind(resolvePropertyNameByLocale(key, locale, fieldDefinition.isI18n()));
184
185 String caption = component.getCaption();
186 if (locale != null && fieldDefinition.isI18n() && !i18NAuthoringSupport.isDefaultLocale(locale)) {
187 caption = String.format("%s (%s)", caption, locale.toString());
188 }
189 fieldLayout.setCaption(caption);
190 fieldLayout.setRequiredIndicatorVisible(field.isRequiredIndicatorVisible());
191
192 localisedComponents.put(key, fieldLayout);
193 });
194
195 formDefinition.getSubFormDefinitions().forEach(def -> {
196 final String key = def.getName();
197 log.debug("Creating form component for {} and locale {}", key, locale);
198
199 ComplexPropertyDefinition<?> asComplexPropertyDef = def;
200 ItemProviderStrategy<?> subFormProviderStrategy = this.create(asComplexPropertyDef.getItemProvider(), def);
201 EditorView<?> subForm = this.create(key, asComplexPropertyDef, subFormProviderStrategy, locale, asComplexPropertyDef);
202 Component component = subForm.getLayout(locale);
203 component.setCaption(def.getLabel());
204 subForms.putIfAbsent(subForm, subFormProviderStrategy);
205 localisedComponents.put(key, component);
206 });
207 return localisedComponents;
208 }
209
210 private Component doCreateLayout(LayoutDefinition<?> definition, Map<String, Component> components, Binder<T> binder) {
211 final LayoutProducer<LayoutDefinition> producer = getComponentProvider().newInstance(definition.getImplementationClass(), binder);
212 final Component layout = producer.createLayout(definition, components);
213 layout.setSizeFull();
214 return layout;
215 }
216
217 private String resolvePropertyNameByLocale(String name, Locale locale, boolean isI18nProperty) {
218 if (isI18nProperty && !i18NAuthoringSupport.isDefaultLocale(locale)) {
219 return i18NAuthoringSupport.deriveLocalisedPropertyName(name, locale);
220 }
221 return name;
222 }
223 }