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