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;
35
36 import static java.util.Collections.singletonList;
37
38 import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
39 import info.magnolia.ui.field.AbstractSelectFieldDefinition;
40 import info.magnolia.ui.field.ComplexPropertyDefinition;
41 import info.magnolia.ui.field.factory.FieldBinder;
42 import info.magnolia.ui.field.factory.FieldFactory;
43 import info.magnolia.ui.field.factory.FormFieldFactory;
44 import info.magnolia.ui.form.OptionDefinition;
45 import info.magnolia.ui.form.SwitchableFormDefinition;
46 import info.magnolia.ui.framework.databinding.view.EditorView;
47 import info.magnolia.ui.framework.datasource.components.PropertySetFactory;
48 import info.magnolia.ui.framework.datasource.impl.FixedSizeDatasourceDefinition;
49 import info.magnolia.ui.framework.ioc.UiContextBoundComponentProvider;
50
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.Map;
56 import java.util.Optional;
57
58 import javax.inject.Inject;
59
60 import org.apache.commons.lang3.tuple.Pair;
61
62 import com.vaadin.data.Binder;
63 import com.vaadin.data.BinderValidationStatus;
64 import com.vaadin.data.HasValue;
65 import com.vaadin.ui.Component;
66 import com.vaadin.ui.Layout;
67 import com.vaadin.ui.VerticalLayout;
68
69
70
71
72
73
74
75 public class SwitchableFormView<T> implements EditorView<T> {
76
77 private final SwitchableFormDefinition<T> definition;
78 private final ItemProviderStrategy<T> itemProviderStrategy;
79 private final Locale locale;
80 private final FormFieldFactory formFieldFactory;
81 private final PropertySetFactory<T> propertySetFactory;
82 private final I18NAuthoringSupport i18NAuthoringSupport;
83
84 private Map<String, Pair<EditorView<T>, ItemProviderStrategy<T>>> subForms = new HashMap<>();
85 private HasValue<OptionDefinition> optionsField;
86 private Binder<T> binder;
87
88 private Layout layout = new VerticalLayout();
89
90 @Inject
91 public SwitchableFormView(SwitchableFormDefinition<T> definition, ItemProviderStrategy<T> itemProviderStrategy, Locale locale, FormFieldFactory formFieldFactory, PropertySetFactory<T> propertySetFactory, I18NAuthoringSupport i18NAuthoringSupport) {
92 this.definition = definition;
93 this.itemProviderStrategy = itemProviderStrategy;
94 this.locale = locale;
95 this.formFieldFactory = formFieldFactory;
96 this.propertySetFactory = propertySetFactory;
97 this.i18NAuthoringSupport = i18NAuthoringSupport;
98
99 initialize();
100 }
101
102 private void initialize() {
103 AbstractSelectFieldDefinition<OptionDefinition, FixedSizeDatasourceDefinition> fieldDefinition = definition.getField();
104 UiContextBoundComponentProvider componentProvider = getComponentProvider().inChildContext(fieldDefinition);
105
106 FieldFactory<OptionDefinition> fieldFactory = componentProvider.newInstance(fieldDefinition.getFactoryClass());
107 optionsField = fieldFactory.createField();
108
109 layout.addComponent((Component) optionsField);
110
111 optionsField.addValueChangeListener(e -> {
112 if (e.getOldValue() == null || e.getOldValue() == null) {
113 return;
114 }
115
116 EditorView<T> oldView = subForm(e.getOldValue());
117 EditorView<T> newView = subForm(e.getValue());
118
119 layout.replaceComponent(oldView.asVaadinComponent(), newView.asVaadinComponent());
120 });
121
122 definition.getForms().forEach(formDefinition -> {
123 ItemProviderStrategy<T> subFormProviderStrategy = create(((ComplexPropertyDefinition<T>) formDefinition).getItemProvider(), formDefinition, itemProviderStrategy);
124 subForms.put(formDefinition.getName(), Pair.of(create(formDefinition), subFormProviderStrategy));
125 });
126
127 binder = Binder.withPropertySet(propertySetFactory.fromFieldDefinitions(singletonList(fieldDefinition), locale));
128 FieldBinder<OptionDefinition> fieldBinder = componentProvider.newInstance(fieldDefinition.getFieldBinderClass());
129
130 fieldBinder.configureBinding(fieldDefinition, binder.forField(optionsField))
131 .bind(resolvePropertyNameByLocale(fieldDefinition.getName(), locale, fieldDefinition.isI18n()));
132 }
133
134 private String resolvePropertyNameByLocale(String name, Locale locale, boolean isI18nProperty) {
135 if (isI18nProperty && !i18NAuthoringSupport.isDefaultLocale(locale)) {
136 return i18NAuthoringSupport.deriveLocalisedPropertyName(name, locale);
137 }
138 return name;
139 }
140
141 @Override
142 public void populate(T item) {
143 binder.readBean(item);
144
145 if (optionsField.getValue() == null) {
146 defaultSelectedOptionDefinition().ifPresent(optionsField::setValue);
147 }
148
149 Optional.ofNullable(optionsField.getValue()).ifPresent(optionDefinition -> {
150 EditorView<T> subForm = subForm(optionDefinition);
151 subFormItemProvider(optionDefinition).read().ifPresent(subForm::populate);
152 layout.addComponent(subForm.asVaadinComponent());
153 });
154 }
155
156 private Optional<OptionDefinition> defaultSelectedOptionDefinition() {
157 return Optional.ofNullable(definition.getField().getDefaultValue());
158 }
159
160 @Override
161 public void write(T item) {
162 binder.writeBeanIfValid(item);
163
164 Optional.ofNullable(optionsField.getValue()).ifPresent(selectedOptionDefinition ->
165 subFormItemProvider(selectedOptionDefinition).read().ifPresent(t -> subForm(selectedOptionDefinition).write(t)));
166 }
167
168 private ItemProviderStrategy<T> subFormItemProvider(OptionDefinition selectedItem) {
169 return subForms.get(selectedItem.getValue()).getValue();
170 }
171
172 @Override
173 public List<BinderValidationStatus<?>> validate() {
174 return Optional.ofNullable(optionsField.getValue())
175 .map(this::subForm)
176 .map(EditorView::validate)
177 .orElseGet(Collections::emptyList);
178 }
179
180 private EditorView<T> subForm(OptionDefinition selectedOptionDefinition) {
181 return subForms.get(selectedOptionDefinition.getValue()).getKey();
182 }
183
184 @Override
185 public Component getLayout(Locale locale) {
186 return layout;
187 }
188
189 @Override
190 public Component asVaadinComponent() {
191 return layout;
192 }
193 }