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.framework.databinding.ItemProviderStrategy;
40 import info.magnolia.ui.field.ComplexPropertyDefinition;
41 import info.magnolia.ui.field.FieldDefinition;
42 import info.magnolia.ui.form.FormDefinition;
43 import info.magnolia.ui.field.factory.FormFieldFactory;
44 import info.magnolia.ui.framework.databinding.layout.LayoutDefinition;
45 import info.magnolia.ui.framework.databinding.layout.LayoutProducer;
46
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.List;
50 import java.util.Locale;
51 import java.util.Map;
52 import java.util.Optional;
53 import java.util.stream.Stream;
54
55 import javax.inject.Inject;
56
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 import com.vaadin.data.Binder;
61 import com.vaadin.data.BinderValidationStatus;
62 import com.vaadin.data.HasValue;
63 import com.vaadin.data.ValidationException;
64 import com.vaadin.ui.Component;
65
66
67
68
69
70
71
72
73 public class FormView<T> implements EditorView<T> {
74
75 private static final Logger log = LoggerFactory.getLogger(FormView.class);
76
77 private final Map<EditorView<?>, ItemProviderStrategy> subForms = new HashMap<>();
78 private final Map<Locale, Binder<T>> localisedBinders = new HashMap<>();
79 private final Map<Locale, Component> localisedLayouts = new HashMap<>();
80
81 private final I18NAuthoringSupport i18NAuthoringSupport;
82
83 @Inject
84 public FormView(FormDefinition<T> formDefinition,
85 FormFieldFactory formFieldFactory,
86 I18NAuthoringSupport i18NAuthoringSupport) {
87
88 this.i18NAuthoringSupport = i18NAuthoringSupport;
89 final FormPresenter<T> formPresenter = create(FormPresenter.class, this);
90 List<Locale> availableLocales = i18NAuthoringSupport.getAvailableLocales();
91 if (availableLocales.isEmpty()) {
92 availableLocales = Collections.singletonList(i18NAuthoringSupport.getDefaultLocale());
93 }
94
95 availableLocales
96 .forEach(locale -> createLocaleSpecificBinderAndLayout(formFieldFactory, 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(FormFieldFactory formFieldFactory, FormDefinition<T> formDefinition,
153 FormPresenter<T> formPresenter, Locale locale) {
154 localisedBinders.put(locale, formPresenter.createBinder(formDefinition, locale));
155
156 log.debug("Creating layout for form named '{}' and locale {}", formDefinition.getName(), locale);
157 final Map<String, Component> components = initialiseAndGetLocaleSpecificComponents(formDefinition, formFieldFactory, locale);
158 localisedLayouts.computeIfAbsent(locale, l -> doCreateLayout(formDefinition.getLayout(), components));
159 }
160
161 private Map<String, Component> initialiseAndGetLocaleSpecificComponents(FormDefinition<T> formDefinition, FormFieldFactory formFieldFactory, Locale locale) {
162 final Map<String, Component> localisedComponents = new HashMap<>();
163
164 formDefinition.getFieldDefinitions().forEach(fieldDefinition -> {
165 final String key = fieldDefinition.getName();
166 log.debug("Creating component for {} and locale {}", key, locale);
167
168 final HasValue<Object> component = formFieldFactory.createField((FieldDefinition<Object>) fieldDefinition, locale, i18NAuthoringSupport);
169 localisedBinders.get(locale).bind(component, resolvePropertyNameByLocale(key, locale, fieldDefinition.isI18n()));
170 localisedComponents.put(key, (Component) component);
171 });
172
173 formDefinition.getSubFormDefinitions().forEach(def -> {
174 final String key = def.getName();
175 log.debug("Creating form component for {} and locale {}", key, locale);
176
177 final ItemProviderStrategy<?> subFormProviderStrategy = this.create(((ComplexPropertyDefinition<?>) def).getItemProvider(), def);
178 final EditorView<?> subForm = getViewProvider().create(def, subFormProviderStrategy, locale);
179 final Component component = subForm.getLayout(locale);
180 component.setCaption(def.getLabel());
181 subForms.putIfAbsent(subForm, subFormProviderStrategy);
182 localisedComponents.put(key, component);
183 });
184 return localisedComponents;
185 }
186
187 private Component doCreateLayout(LayoutDefinition<?> definition, Map<String, Component> components) {
188 final LayoutProducer<LayoutDefinition> producer = getComponentProvider().newInstance(definition.getImplementationClass());
189 final Component layout = producer.createLayout(definition, components);
190 layout.setSizeFull();
191 return layout;
192 }
193
194 private String resolvePropertyNameByLocale(String name, Locale locale, boolean isI18nProperty) {
195 if (isI18nProperty && !i18NAuthoringSupport.isDefaultLocale(locale)) {
196 return i18NAuthoringSupport.deriveLocalisedPropertyName(name, locale);
197 }
198 return name;
199 }
200 }