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.editor;
35
36 import static java.util.stream.Collectors.*;
37
38 import info.magnolia.ui.field.EditorPropertyDefinition;
39 import info.magnolia.ui.field.FieldDefinition;
40 import info.magnolia.ui.field.ValueBoundProperty;
41 import info.magnolia.ui.framework.layout.FieldLayoutDefinition;
42 import info.magnolia.ui.framework.layout.FieldLayoutProducer;
43 import info.magnolia.ui.framework.layout.LayoutDefinition;
44
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.LinkedHashMap;
48 import java.util.List;
49 import java.util.Locale;
50 import java.util.Map;
51 import java.util.Optional;
52 import java.util.function.Function;
53 import java.util.stream.Stream;
54
55 import javax.inject.Inject;
56
57 import com.vaadin.data.Binder;
58 import com.vaadin.data.BinderValidationStatus;
59 import com.vaadin.ui.Component;
60 import com.vaadin.ui.ComponentContainer;
61 import com.vaadin.ui.HasComponents;
62
63 import lombok.AllArgsConstructor;
64 import lombok.Getter;
65
66
67
68
69
70
71
72
73 public class FormView<T> implements EditorView<T>, ItemEditor {
74
75 private final List<SubEditorReference<T>> subEditors = new ArrayList<>();
76 private final LocaleContext localeContext;
77
78 private final FormDefinition<T> formDefinition;
79 private final FormPresenter<T> formPresenter;
80 private Component layout;
81
82 @Inject
83 public FormView(FormDefinition<T> formDefinition, LocaleContext localeContext) {
84
85 this.formDefinition = formDefinition;
86 this.localeContext = localeContext;
87
88 this.formPresenter = create(FormPresenter.class, formDefinition, localeContext.getDefault());
89 this.createComplexProperties();
90
91 if (!localeContext.current().value().isPresent()) {
92 displayLocalisedVersion(localeContext.getDefault());
93 }
94
95 localeContext.current().observe(optionalLocale -> optionalLocale.ifPresent(this::displayLocalisedVersion));
96 }
97
98 private void createComplexProperties() {
99 formDefinition.getSubFormDefinitions().forEach(def -> {
100 ComplexPropertyDefinition<T> asComplexPropertyDef = def;
101 ItemProviderStrategy<T, T> subFormProviderStrategy = this.create(asComplexPropertyDef.getItemProvider(), def);
102 Map<Locale, EditorView<T>> localisedRepresentations = new HashMap<>();
103
104
105 if (asComplexPropertyDef.isI18n()) {
106 localeContext.availableLocales().nullableValue().forEach(locale -> {
107 LocaleContext childLocaleContext = LocaleContext.of(locale);
108 EditorView<T> subForm = createSubEditor(asComplexPropertyDef, childLocaleContext, subFormProviderStrategy);
109 localisedRepresentations.put(locale, subForm);
110 });
111 } else {
112
113
114 localisedRepresentations.put(localeContext.getDefault(), createSubEditor(asComplexPropertyDef, this.localeContext, subFormProviderStrategy));
115 }
116
117 this.subEditors.add(new SubEditorReference<>(localisedRepresentations, subFormProviderStrategy, asComplexPropertyDef));
118 });
119 }
120
121 private EditorView<T> createSubEditor(ComplexPropertyDefinition<T> definition, LocaleContext localeContext, Object... args) {
122 EditorView<T> subEditor = this.create(definition.getEditorDefinition(), localeContext, args);
123 String caption = definition.getLabel();
124 if (definition.isI18n() && this.localeContext.getAvailableLocales().count() > 1) {
125 subEditor.bindInstance(LocaleContext.class, localeContext);
126 caption = String.format("%s (%s)", caption, localeContext.getDefault().toString());
127 }
128 subEditor.asVaadinComponent().addStyleName(definition.getStyleName());
129 subEditor.asVaadinComponent().setCaption(caption);
130 return subEditor;
131 }
132
133 @Override
134 public <V> Optional<V> getPropertyValue(String propertyName) {
135 return this.getPropertyValue(propertyName, localeContext.getCurrent());
136 }
137
138 @Override
139 public void populate(T item) {
140 formPresenter.bind(item);
141 subEditors.forEach(ref -> {
142 ItemProviderStrategy<T, T> itemProviderStrategy = ref.itemProviderStrategy;
143 ref.representations.forEach((locale, subForm) -> itemProviderStrategy.read(item, locale).ifPresent(subForm::populate));
144 });
145 }
146
147 @Override
148 public void write(T localisedItemSources) {
149 formPresenter.writeBindings(localisedItemSources);
150 subEditors.forEach(ref -> {
151 ItemProviderStrategy<T, T> itemProviderStrategy = ref.itemProviderStrategy;
152 ref.representations.forEach((locale, subForm) -> itemProviderStrategy.read(localisedItemSources, locale).ifPresent(subForm::write));
153 });
154 }
155
156 @Override
157 public void applyDefaults() {
158 this.formPresenter.applyDefaults();
159 this.subEditors.stream()
160 .map(ref -> ref.getRepresentations().get(localeContext.getDefault()))
161 .forEach(EditorView::applyDefaults);
162 }
163
164 private void displayLocalisedVersion(Locale locale) {
165 formPresenter.getBoundFields(locale).ifPresent(boundFields -> {
166 Component old = this.layout;
167 this.layout = computeLayout(boundFields);
168 HasComponents parent = old != null ? old.getParent() : null;
169 if (parent instanceof ComponentContainer) {
170 this.layout.addStyleNames(old.getStyleName());
171 ((ComponentContainer) parent).replaceComponent(old, this.layout);
172 }
173 });
174 }
175
176
177
178
179
180 @Override
181 public List<BinderValidationStatus<?>> validate() {
182 final Stream<BinderValidationStatus<T>> fieldValidationResults = formPresenter.validateBoundProperties();
183
184 final Stream<BinderValidationStatus<?>> subFormValidationResults = subEditors.stream()
185 .flatMap(subEditorReference -> subEditorReference.representations.values().stream())
186 .flatMap(childForm -> childForm.validate().stream());
187
188 return Stream.concat(fieldValidationResults, subFormValidationResults).collect(toList());
189 }
190
191 @Override
192 public <V> Optional<V> getPropertyValue(String propertyName, Locale locale) {
193 return this.formPresenter.getBoundPropertyValue(propertyName, locale);
194 }
195
196 @Override
197 public Stream<String> getPropertyNames() {
198 return formDefinition.getProperties().stream().map(EditorPropertyDefinition::getName);
199 }
200
201 private Component computeLayout(Map<FieldDefinition<T>, Component> boundFields) {
202
203 FieldLayoutDefinition<?> layoutDefinition = formDefinition.getLayout();
204 @SuppressWarnings("unchecked")
205 FieldLayoutProducer<LayoutDefinition> producer = getComponentProvider().newInstance(layoutDefinition.getImplementationClass());
206
207 Map<String, Component> fieldComponents = new LinkedHashMap<>();
208 subEditors.forEach(subForm -> {
209 Locale targetLocale = subForm.definition.isI18n() ? localeContext.getCurrent() : localeContext.getDefault();
210 fieldComponents.put(subForm.definition.getName(), subForm.representations.get(targetLocale).asVaadinComponent());
211 });
212
213 boundFields.forEach((def, component) -> fieldComponents.put(def.getName(), component));
214 Map<EditorPropertyDefinition, Component> orderedComponents = this.formDefinition.getProperties().stream().collect(
215 toMap(Function.identity(),
216 def -> fieldComponents.get(def.getName()),
217 (f, s) -> s, LinkedHashMap::new));
218
219 return producer.createLayout(layoutDefinition, orderedComponents);
220 }
221
222 @Override
223 public Component asVaadinComponent() {
224 return this.layout;
225 }
226
227 @Getter
228 @AllArgsConstructor
229 private static class SubEditorReference<T> {
230 private Map<Locale, EditorView<T>> representations;
231 private ItemProviderStrategy<T, T> itemProviderStrategy;
232 private ComplexPropertyDefinition<T> definition;
233 }
234 }