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.Collections.singletonList;
37 import static java.util.stream.Collectors.toMap;
38
39 import com.google.common.base.Objects;
40 import info.magnolia.ui.datasource.PropertySetFactory;
41 import info.magnolia.ui.datasource.optionlist.Option;
42 import info.magnolia.ui.datasource.optionlist.OptionListDefinition;
43 import info.magnolia.ui.field.AbstractSelectFieldDefinition;
44 import info.magnolia.ui.field.FieldBinder;
45 import info.magnolia.ui.field.WithPropertyNameDecorator;
46 import info.magnolia.ui.framework.ioc.UiComponentProvider;
47 import info.magnolia.ui.framework.layout.PlainFormLayoutDefinition;
48
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Locale;
52 import java.util.Map;
53 import java.util.Optional;
54
55 import javax.inject.Inject;
56
57 import com.vaadin.data.Binder;
58 import com.vaadin.data.BinderValidationStatus;
59 import com.vaadin.data.HasValue;
60 import com.vaadin.ui.Component;
61 import com.vaadin.ui.VerticalLayout;
62
63
64
65
66
67
68
69
70 public class SwitchableFormView<T> implements EditorView<T> {
71
72 private final SwitchableFormDefinition<T> definition;
73
74 private final PropertySetFactory<T> propertySetFactory;
75
76 private final Binder<T> binder;
77
78 private final Map<String, EditorView<T>> representations;
79
80 private final Locale locale;
81
82 private VerticalLayout layout = new VerticalLayout();
83
84 @Inject
85 public SwitchableFormView(SwitchableFormDefinition<T> definition, PropertySetFactory<T> propertySetFactory, LocaleContext localeContext) {
86 this.definition = definition;
87 this.propertySetFactory = propertySetFactory;
88 this.representations = definition.getForms().stream()
89 .collect(toMap(
90 EditorDefinition::getName,
91 childEditor -> this.create(new CompositeFormDefinition<>(childEditor, definition.getPropertyNameDecorator()))));
92 this.locale = localeContext.getDefault();
93 this.binder = bindOptionField();
94
95 layout();
96 }
97
98 @Override
99 public void populate(T item) {
100 this.binder.readBean(item);
101 this.representations.values().forEach(representation -> representation.populate(item));
102
103 layout();
104 }
105
106 @Override
107 public List<BinderValidationStatus<?>> validate() {
108 Option currentRepresentation = getOptionField().getValue();
109 return this.subForm(currentRepresentation)
110 .map(EditorView::validate)
111 .orElseGet(Collections::emptyList);
112 }
113
114 @Override
115 public void write(T item) {
116 this.binder.writeBeanIfValid(item);
117 subForm(getOptionField().getValue()).ifPresent(form -> form.write(item));
118 }
119
120 @Override
121 public void applyDefaults() {
122 definition.getField().getDatasource().getOptions().stream()
123 .filter(option -> Objects.equal(option.getValue(), definition.getField().getDefaultValue()))
124 .findFirst()
125 .ifPresent(defaultOption -> {
126 getOptionField().setValue(defaultOption);
127 subForm(defaultOption).ifPresent(EditorView::applyDefaults);
128 });
129 }
130
131 private void layout() {
132 layout.removeAllComponents();
133 layout.setMargin(false);
134 HasValue<Option> optionField = getOptionField();
135 layout.addComponent((Component) optionField);
136
137 ((Component) optionField).setCaption(null);
138 optionField.getOptionalValue()
139 .flatMap(this::subForm)
140 .ifPresent(view -> layout.addComponent(view.asVaadinComponent()));
141 }
142
143 private Binder<T> bindOptionField() {
144 AbstractSelectFieldDefinition<Option, OptionListDefinition> fieldDefinition = definition.getField();
145 UiComponentProvider componentProvider = getComponentProvider().inChildContext(fieldDefinition);
146 Binder<T> binder = Binder.withPropertySet(propertySetFactory.fromFieldDefinitions(singletonList(fieldDefinition), this.locale));
147
148 FieldBinder<Option> fieldBinder = componentProvider.newInstance(fieldDefinition.getFieldBinderClass());
149 HasValue<Option> optionsField = componentProvider.newInstance(fieldDefinition.getFactoryClass()).createField();
150 fieldBinder
151 .configureBinding(fieldDefinition, binder.forField(optionsField))
152 .bind(fieldDefinition.getName());
153
154 optionsField.addValueChangeListener(e -> {
155 subForm(e.getOldValue()).ifPresent(oldView -> layout.removeComponent(oldView.asVaadinComponent()));
156 subForm(e.getValue()).ifPresent(newView -> layout.addComponent(newView.asVaadinComponent()));
157 });
158
159 return binder;
160 }
161
162 private HasValue<Option> getOptionField() {
163 return binder.getBinding(definition.getField().getName())
164 .map(binding -> (HasValue<Option>) binding.getField())
165 .orElse(null);
166 }
167
168 private Optional<EditorView<T>> subForm(Option selectedOption) {
169 return Optional.ofNullable(selectedOption).map(option -> representations.get(option.getValue()));
170 }
171
172 private class CompositeFormDefinition<T> extends ConfiguredFormDefinition<T> implements WithPropertyNameDecorator {
173
174 private final Class<PropertyNameDecorator> propertyNameDecorator;
175
176 public CompositeFormDefinition(FormDefinition<T> definition, Class<PropertyNameDecorator> propertyNameDecorator) {
177 this.setLayout(new PlainFormLayoutDefinition());
178 this.setName(definition.getName());
179 this.setProperties(definition.getProperties());
180 this.propertyNameDecorator = propertyNameDecorator;
181 }
182
183 @Override
184 public Class getPropertyNameDecorator() {
185 return propertyNameDecorator;
186 }
187 }
188
189 @Override
190 public Component asVaadinComponent() {
191 return layout;
192 }
193 }