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.form.field;
35
36 import info.magnolia.cms.i18n.I18nContentSupport;
37 import info.magnolia.icons.MagnoliaIcons;
38 import info.magnolia.objectfactory.ComponentProvider;
39 import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
40 import info.magnolia.ui.form.field.definition.ConfiguredFieldDefinition;
41 import info.magnolia.ui.form.field.definition.MultiValueFieldDefinition;
42 import info.magnolia.ui.form.field.factory.FieldFactoryFactory;
43 import info.magnolia.ui.form.field.transformer.TransformedProperty;
44 import info.magnolia.ui.form.field.transformer.Transformer;
45 import info.magnolia.ui.form.field.transformer.multi.MultiTransformer;
46 import info.magnolia.ui.framework.ioc.AdmincentralFlavour;
47 import info.magnolia.ui.theme.ResurfaceTheme;
48
49 import java.util.stream.StreamSupport;
50
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 import com.google.common.base.Function;
55 import com.google.common.base.Optional;
56 import com.google.common.base.Predicates;
57 import com.google.common.collect.Iterators;
58 import com.vaadin.ui.Alignment;
59 import com.vaadin.ui.Button;
60 import com.vaadin.ui.Component;
61 import com.vaadin.ui.HasComponents;
62 import com.vaadin.ui.NativeButton;
63 import com.vaadin.v7.data.Item;
64 import com.vaadin.v7.data.Property;
65 import com.vaadin.v7.data.util.PropertysetItem;
66 import com.vaadin.v7.ui.Field;
67 import com.vaadin.v7.ui.HorizontalLayout;
68 import com.vaadin.v7.ui.VerticalLayout;
69
70
71
72
73
74
75
76
77
78 public class MultiField extends AbstractCustomMultiField<MultiValueFieldDefinition, PropertysetItem> {
79
80 private static final Logger log = LoggerFactory.getLogger(MultiField.class);
81
82 private final ConfiguredFieldDefinition fieldDefinition;
83
84 private final Button addButton = new NativeButton();
85 private String buttonCaptionAdd;
86 private String buttonCaptionRemove;
87 private String buttonCaptionMoveUp = "Move Up";
88 private String buttonCaptionMoveDown = "Move Down";
89 private boolean isOrderable = true;
90
91 public MultiField(MultiValueFieldDefinition definition, FieldFactoryFactory fieldFactoryFactory, ComponentProvider componentProvider, Item relatedFieldItem, I18NAuthoringSupport i18nAuthoringSupport) {
92 super(definition, fieldFactoryFactory, componentProvider, relatedFieldItem, i18nAuthoringSupport);
93 this.fieldDefinition = definition.getField();
94
95 if (definition.isReadOnly()) {
96 fieldDefinition.setReadOnly(true);
97 }
98 }
99
100
101
102
103 @Deprecated
104 public MultiField(MultiValueFieldDefinition definition, FieldFactoryFactory fieldFactoryFactory, I18nContentSupport i18nContentSupport, ComponentProvider componentProvider, Item relatedFieldItem) {
105 this(definition, fieldFactoryFactory, componentProvider, relatedFieldItem, null);
106 }
107
108 @Override
109 protected Component initContent() {
110
111 addStyleNames("linkfield", "multifield");
112 addStyleName("force-show-field-captions");
113 root = new VerticalLayout();
114 root.setSpacing(true);
115 root.setWidth(100, Unit.PERCENTAGE);
116 root.setHeight(-1, Unit.PIXELS);
117
118
119 addButton.setCaption(buttonCaptionAdd);
120 addButton.addStyleNames("magnoliabutton", "add");
121 addButton.addClickListener(clickEvent -> {
122 int newPropertyId;
123 Property<?> property = null;
124
125 Transformer<?> transformer = ((TransformedProperty<?>) getPropertyDataSource()).getTransformer();
126 PropertysetItem item = (PropertysetItem) getPropertyDataSource().getValue();
127
128 if (transformer instanceof MultiTransformer) {
129
130 property = ((MultiTransformer) transformer).createProperty();
131 newPropertyId = findPropertyId(item, property);
132 } else {
133
134 newPropertyId = item.getItemPropertyIds().size();
135 }
136
137 if (newPropertyId == -1) {
138 log.warn("Could not resolve new propertyId; cannot add new multifield entry to item '{}'.", item);
139 return;
140 }
141
142 root.addComponent(createEntryComponent(newPropertyId, property), root.getComponentCount() - 1);
143 });
144
145
146 initFields();
147
148 return root;
149 }
150
151
152
153
154
155 @Override
156 protected void initFields(PropertysetItem newValue) {
157 root.removeAllComponents();
158 for (Object propertyId : newValue.getItemPropertyIds()) {
159 Property<?> property = newValue.getItemProperty(propertyId);
160 root.addComponent(createEntryComponent(propertyId, property));
161 }
162 if (!this.definition.isReadOnly()) {
163 root.addComponent(addButton);
164 }
165 }
166
167
168
169
170
171
172
173 private Component createEntryComponent(Object propertyId, Property<?> property) {
174 final HorizontalLayout layout = new HorizontalLayout();
175 layout.setWidth(100, Unit.PERCENTAGE);
176 layout.setHeight(-1, Unit.PIXELS);
177
178 final Field<?> field = createLocalField(fieldDefinition, property, true);
179 layout.addComponent(field);
180
181
182 if (property == null) {
183 property = field.getPropertyDataSource();
184 ((PropertysetItem) getPropertyDataSource().getValue()).addItemProperty(propertyId, property);
185 }
186 final Property<?> propertyReference = property;
187
188 layout.setWidth(100, Unit.PERCENTAGE);
189
190
191 layout.setExpandRatio(field, 1);
192 if (definition.isReadOnly()) {
193 return layout;
194 }
195
196 if (isOrderable()) {
197
198 Button moveUpButton = new Button(MagnoliaIcons.ARROW2_N, clickEvent -> {
199 onMove(layout, propertyReference, true);
200 clickEvent.getButton().focus();
201 });
202 moveUpButton.addStyleNames("inline", "move-up");
203 moveUpButton.setDescription(buttonCaptionMoveUp);
204
205
206 final Button moveDownButton = new Button(MagnoliaIcons.ARROW2_S, clickEvent -> {
207 onMove(layout, propertyReference, false);
208 clickEvent.getButton().focus();
209 });
210 moveDownButton.addStyleNames("inline", "move-down");
211 moveDownButton.setDescription(buttonCaptionMoveDown);
212
213 layout.addComponents(moveUpButton, moveDownButton);
214
215 layout.setComponentAlignment(moveUpButton, Alignment.BOTTOM_RIGHT);
216 layout.setComponentAlignment(moveDownButton, Alignment.BOTTOM_RIGHT);
217 }
218
219
220 Button deleteButton = new Button(MagnoliaIcons.TRASH, clickEvent -> onDelete(layout, propertyReference));
221 deleteButton.addStyleNames("inline", "trash");
222 deleteButton.setDescription(buttonCaptionRemove);
223
224 layout.addComponents(deleteButton);
225 layout.setComponentAlignment(deleteButton, Alignment.BOTTOM_RIGHT);
226
227 if (!AdmincentralFlavour.get().isM5()) {
228 StreamSupport.stream(layout.spliterator(), false)
229 .filter(Button.class::isInstance)
230 .forEach(button -> {
231 button.addStyleName(ResurfaceTheme.BUTTON_ICON);
232 layout.setComponentAlignment(button, Alignment.MIDDLE_RIGHT);
233 });
234 }
235
236 return layout;
237 }
238
239 @Override
240 public Class<? extends PropertysetItem> getType() {
241 return PropertysetItem.class;
242 }
243
244
245
246
247 public void setButtonCaptionAdd(String buttonCaptionAdd) {
248 this.buttonCaptionAdd = buttonCaptionAdd;
249 }
250
251 public void setButtonCaptionRemove(String buttonCaptionRemove) {
252 this.buttonCaptionRemove = buttonCaptionRemove;
253 }
254
255
256
257
258
259
260
261 private void removeValueProperty(int fromIndex) {
262 getValue().removeItemProperty(fromIndex);
263 int valuesSize = getValue().getItemPropertyIds().size();
264 if (fromIndex == valuesSize) {
265 return;
266 }
267 while (fromIndex < valuesSize) {
268 int toIndex = fromIndex;
269 fromIndex +=1;
270 getValue().addItemProperty(toIndex, getValue().getItemProperty(fromIndex));
271 getValue().removeItemProperty(fromIndex);
272 }
273 }
274
275
276
277
278 private void switchItemProperties(Object firstPropertyId, Object secondPropertyId) {
279 Property propertyFirst = getValue().getItemProperty(firstPropertyId);
280 Property propertySecond = getValue().getItemProperty(secondPropertyId);
281
282 try {
283 PropertysetItem storedValues = (PropertysetItem) getValue().clone();
284 if (storedValues != null) {
285 for (Object propertyId : storedValues.getItemPropertyIds()) {
286 getValue().removeItemProperty(propertyId);
287 if (propertyId == firstPropertyId) {
288 getValue().addItemProperty(firstPropertyId, propertySecond);
289 } else if (propertyId == secondPropertyId) {
290 getValue().addItemProperty(secondPropertyId, propertyFirst);
291 } else {
292 getValue().addItemProperty(propertyId, storedValues.getItemProperty(propertyId));
293 }
294 }
295 getPropertyDataSource().setValue(getValue());
296 }
297 } catch (CloneNotSupportedException e) {
298 log.error("Unable to switch properties on MultiField. Unable to clone PropertysetItem.", e);
299 }
300
301 }
302
303 private void onDelete(Component layout, Property<?> propertyReference) {
304 root.removeComponent(layout);
305 Transformer<?> transformer = ((TransformedProperty<?>) getPropertyDataSource()).getTransformer();
306
307
308 Object propertyId = findPropertyId(getValue(), propertyReference);
309
310 if (transformer instanceof MultiTransformer) {
311 ((MultiTransformer) transformer).removeProperty(propertyId);
312 } else {
313 if (propertyId.getClass().isAssignableFrom(Integer.class)) {
314 removeValueProperty((Integer) propertyId);
315 } else {
316 log.error("Property id {} is not an integer and as such property can't be removed", propertyId);
317 }
318 getPropertyDataSource().setValue(getValue());
319 }
320 }
321
322
323
324
325
326 private void onMove(Component layout, Property<?> propertyReference, boolean moveUp) {
327 int currentPosition = root.getComponentIndex(layout);
328 int switchPosition = currentPosition + (moveUp ? -1 : 1);
329
330 Field[] fields = Iterators.toArray(Iterators.filter(Iterators.transform(root.iterator(), new Function<Component, Field>() {
331 @Override
332 public Field apply(Component input) {
333 if (input instanceof HasComponents) {
334 Optional<Component> field = Iterators.tryFind(((HasComponents) input).iterator(), Predicates.instanceOf(Field.class));
335 if (field.isPresent()) {
336 return (Field) field.get();
337 }
338 }
339 return null;
340 }
341 }), Predicates.notNull()), Field.class);
342
343 if (moveUp && currentPosition != 0 || (!moveUp && currentPosition != fields.length - 1)) {
344 Field switchField = fields[switchPosition];
345 Object currentPropertyId = MultiField.this.findPropertyId(getValue(), propertyReference);
346 Object switchPropertyId = MultiField.this.findPropertyId(getValue(), switchField.getPropertyDataSource());
347
348 root.replaceComponent(root.getComponent(currentPosition), root.getComponent(switchPosition));
349 switchItemProperties(currentPropertyId, switchPropertyId);
350 }
351 }
352
353 public boolean isOrderable() {
354 return isOrderable;
355 }
356
357 public void setOrderable(boolean orderable) {
358 isOrderable = orderable;
359 }
360 }