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