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.config.MutableWrapper;
37 import info.magnolia.i18nsystem.SimpleTranslator;
38 import info.magnolia.icons.MagnoliaIcons;
39 import info.magnolia.ui.form.FormDefinitionMutator;
40 import info.magnolia.ui.form.MultiFormDefinition;
41 import info.magnolia.ui.framework.databinding.view.EditorView;
42 import info.magnolia.ui.theme.ResurfaceTheme;
43
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.Comparator;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.Locale;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.Optional;
53 import java.util.stream.Collectors;
54 import java.util.stream.Stream;
55
56 import javax.inject.Inject;
57
58 import com.vaadin.data.BinderValidationStatus;
59 import com.vaadin.ui.Button;
60 import com.vaadin.ui.Component;
61 import com.vaadin.ui.HorizontalLayout;
62 import com.vaadin.ui.VerticalLayout;
63
64
65
66
67
68 public class MultiFormView<T> implements EditorView<T> {
69
70 private final MultiFormDefinition<T> definition;
71 private final ItemProviderStrategy<T> multiFormItemProvider;
72 private final Locale locale;
73 private final SimpleTranslator i18n;
74
75 private final MultiFieldOrderHandler<T> orderHandler;
76 private final Map<EditorView<T>, ItemProviderStrategy<T>> forms = new HashMap<>();
77 private final List<EditorView<T>> removedForms = new ArrayList<>();
78 private final List<EditorView<T>> formOrder = new ArrayList<>();
79 private final VerticalLayout layout = new VerticalLayout();
80
81 private enum MoveDirection {
82 UP, DOWN
83 }
84
85 @Inject
86 public MultiFormView(MultiFormDefinition<T> definition, ItemProviderStrategy<T> multiFormItemProvider, Locale locale, SimpleTranslator i18n) {
87 this.definition = definition;
88 this.multiFormItemProvider = multiFormItemProvider;
89 this.locale = locale;
90 this.i18n = i18n;
91 this.orderHandler = this.create(this.definition.getOrderHandler());
92
93 initialize();
94 }
95
96 private void initialize() {
97 Button addButton = new Button(this.i18n.translate("buttons.add"));
98 addButton.addClickListener(e -> addSubForm(layout, null));
99 layout.addComponent(addButton);
100 }
101
102 @Override
103 public void populate(T item) {
104 getFormViews().forEach(EditorView::destroy);
105
106 orderHandler.readItems(item, locale).forEach(itemProvider -> {
107 final EditorView<T> subForm = addSubForm(layout, itemProvider);
108 itemProvider.read().ifPresent(subForm::populate);
109 });
110 }
111
112 @Override
113 public void write(T item) {
114 final List<T> itemsToRemove =
115 getForms()
116 .filter(e -> removedForms.contains(e.getKey()))
117 .map(e -> e.getValue().read())
118 .filter(Optional::isPresent)
119 .map(Optional::get)
120 .collect(Collectors.toList());
121
122 orderHandler.removeItems(itemsToRemove);
123
124 getForms()
125 .filter(e -> !removedForms.contains(e.getKey()))
126 .forEach(e -> e.getValue().read().ifPresent(t -> e.getKey().write(t)));
127
128 final List<T> childItems =
129 getForms()
130 .filter(e -> !removedForms.contains(e.getKey()))
131 .sorted(Comparator.comparingInt(o -> formOrder.indexOf(o.getKey())))
132 .map(e -> e.getValue().read().orElse(null))
133 .filter(Objects::nonNull)
134 .collect(Collectors.toList());
135
136 orderHandler.applyOrder(item, childItems, locale);
137 }
138
139 @Override
140 public List<BinderValidationStatus<?>> validate() {
141 return getFormViews()
142 .flatMap(view -> view.validate().stream())
143 .collect(Collectors.toList());
144 }
145
146 @Override
147 public Component asVaadinComponent() {
148 return getLayout(locale);
149 }
150
151 @Override
152 public Component getLayout(Locale locale) {
153 return layout;
154 }
155
156 private Stream<EditorView<T>> getFormViews() {
157 return forms.keySet().stream();
158 }
159
160 private Stream<Map.Entry<EditorView<T>, ItemProviderStrategy<T>>> getForms() {
161 return forms.entrySet().stream();
162 }
163
164 private EditorView<T> addSubForm(VerticalLayout parent, ItemProviderStrategy<T> itemProvider) {
165
166 MultiFormDefinition<T> multiFormDefinitionClone = MutableWrapper.wrap(definition);
167 FormDefinitionMutator.accessMutable(multiFormDefinitionClone.getForm())
168 .setName(getSubFormName(multiFormDefinitionClone));
169
170 if (itemProvider == null) {
171 itemProvider = () -> multiFormItemProvider.read().map(t -> orderHandler.getOrCreate(t, multiFormDefinitionClone.getForm().getName(), locale));
172 }
173
174 EditorView<T> subForm = create(multiFormDefinitionClone.getForm(), itemProvider);
175
176 forms.put(subForm, itemProvider);
177 formOrder.add(subForm);
178
179
180 VerticalLayout wrapLayout = new VerticalLayout();
181 HorizontalLayout buttonLayout = new HorizontalLayout();
182 buttonLayout.addComponents(
183 new Button(MagnoliaIcons.ARROW2_N, e -> onMove(parent, wrapLayout, MoveDirection.UP)),
184 new Button(MagnoliaIcons.ARROW2_S, e -> onMove(parent, wrapLayout, MoveDirection.DOWN)),
185 new Button(MagnoliaIcons.TRASH, e -> onDelete(parent, subForm)));
186 buttonLayout.forEach(button -> button.addStyleName(ResurfaceTheme.BUTTON_ICON));
187
188 wrapLayout.addComponents(subForm.asVaadinComponent(), buttonLayout);
189 parent.addComponent(wrapLayout, parent.getComponentCount() - 1);
190
191 return subForm;
192 }
193
194 private void onMove(VerticalLayout root, VerticalLayout formLayout, MoveDirection moveDirection) {
195 int currentPosition = root.getComponentIndex(formLayout);
196 int newPosition = moveDirection == MoveDirection.UP ? currentPosition - 1 : currentPosition + 1;
197
198 if (currentPosition == 0 && moveDirection.equals(MoveDirection.UP)) {
199 return;
200 }
201
202 if (newPosition >= root.getComponentCount() - 1) {
203 return;
204 }
205
206 Collections.swap(formOrder, currentPosition, newPosition);
207 root.replaceComponent(root.getComponent(currentPosition), root.getComponent(newPosition));
208 }
209
210 private void onDelete(VerticalLayout root, EditorView<T> subForm) {
211 root.removeComponent(subForm.asVaadinComponent().getParent());
212 removedForms.add(subForm);
213 formOrder.remove(subForm);
214 }
215
216 private String getSubFormName(MultiFormDefinition<T> definition) {
217 return definition.getName() + forms.size();
218 }
219 }