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 root = new VerticalLayout();
113 root.setSpacing(true);
114 root.setWidth(100, Unit.PERCENTAGE);
115 root.setHeight(-1, Unit.PIXELS);
116
117
118 addButton.setCaption(buttonCaptionAdd);
119 addButton.addStyleNames("magnoliabutton", "add");
120 addButton.addClickListener(clickEvent -> {
121 int newPropertyId;
122 Property<?> property = null;
123
124 Transformer<?> transformer = ((TransformedProperty<?>) getPropertyDataSource()).getTransformer();
125 PropertysetItem item = (PropertysetItem) getPropertyDataSource().getValue();
126
127 if (transformer instanceof MultiTransformer) {
128
129 property = ((MultiTransformer) transformer).createProperty();
130 newPropertyId = findPropertyId(item, property);
131 } else {
132
133 newPropertyId = item.getItemPropertyIds().size();
134 }
135
136 if (newPropertyId == -1) {
137 log.warn("Could not resolve new propertyId; cannot add new multifield entry to item '{}'.", item);
138 return;
139 }
140
141 root.addComponent(createEntryComponent(newPropertyId, property), root.getComponentCount() - 1);
142 });
143
144
145 initFields();
146
147 return root;
148 }
149
150
151
152
153
154 @Override
155 protected void initFields(PropertysetItem newValue) {
156 root.removeAllComponents();
157 for (Object propertyId : newValue.getItemPropertyIds()) {
158 Property<?> property = newValue.getItemProperty(propertyId);
159 root.addComponent(createEntryComponent(propertyId, property));
160 }
161 if (!this.definition.isReadOnly()) {
162 root.addComponent(addButton);
163 }
164 }
165
166
167
168
169
170
171
172 private Component createEntryComponent(Object propertyId, Property<?> property) {
173 final HorizontalLayout layout = new HorizontalLayout();
174 layout.setWidth(100, Unit.PERCENTAGE);
175 layout.setHeight(-1, Unit.PIXELS);
176
177 final Field<?> field = createLocalField(fieldDefinition, property, true);
178 layout.addComponent(field);
179
180
181 if (property == null) {
182 property = field.getPropertyDataSource();
183 ((PropertysetItem) getPropertyDataSource().getValue()).addItemProperty(propertyId, property);
184 }
185 final Property<?> propertyReference = property;
186
187 layout.setWidth(100, Unit.PERCENTAGE);
188
189
190 layout.setExpandRatio(field, 1);
191 if (definition.isReadOnly()) {
192 return layout;
193 }
194
195 if (isOrderable()) {
196
197 Button moveUpButton = new Button(MagnoliaIcons.ARROW2_N, clickEvent -> {
198 onMove(layout, propertyReference, true);
199 clickEvent.getButton().focus();
200 });
201 moveUpButton.addStyleNames("inline", "move-up");
202 moveUpButton.setDescription(buttonCaptionMoveUp);
203
204
205 final Button moveDownButton = new Button(MagnoliaIcons.ARROW2_S, clickEvent -> {
206 onMove(layout, propertyReference, false);
207 clickEvent.getButton().focus();
208 });
209 moveDownButton.addStyleNames("inline", "move-down");
210 moveDownButton.setDescription(buttonCaptionMoveDown);
211
212 layout.addComponents(moveUpButton, moveDownButton);
213
214 layout.setComponentAlignment(moveUpButton, Alignment.BOTTOM_RIGHT);
215 layout.setComponentAlignment(moveDownButton, Alignment.BOTTOM_RIGHT);
216 }
217
218
219 Button deleteButton = new Button(MagnoliaIcons.TRASH, clickEvent -> onDelete(layout, propertyReference));
220 deleteButton.addStyleNames("inline", "trash");
221 deleteButton.setDescription(buttonCaptionRemove);
222
223 layout.addComponents(deleteButton);
224 layout.setComponentAlignment(deleteButton, Alignment.BOTTOM_RIGHT);
225
226 if (!AdmincentralFlavour.get().isM5()) {
227 StreamSupport.stream(layout.spliterator(), false)
228 .filter(Button.class::isInstance)
229 .forEach(button -> {
230 button.addStyleName(ResurfaceTheme.BUTTON_ICON);
231 layout.setComponentAlignment(button, Alignment.MIDDLE_RIGHT);
232 });
233 }
234
235 return layout;
236 }
237
238 @Override
239 public Class<? extends PropertysetItem> getType() {
240 return PropertysetItem.class;
241 }
242
243
244
245
246 public void setButtonCaptionAdd(String buttonCaptionAdd) {
247 this.buttonCaptionAdd = buttonCaptionAdd;
248 }
249
250 public void setButtonCaptionRemove(String buttonCaptionRemove) {
251 this.buttonCaptionRemove = buttonCaptionRemove;
252 }
253
254
255
256
257
258
259
260 private void removeValueProperty(int fromIndex) {
261 getValue().removeItemProperty(fromIndex);
262 int valuesSize = getValue().getItemPropertyIds().size();
263 if (fromIndex == valuesSize) {
264 return;
265 }
266 while (fromIndex < valuesSize) {
267 int toIndex = fromIndex;
268 fromIndex +=1;
269 getValue().addItemProperty(toIndex, getValue().getItemProperty(fromIndex));
270 getValue().removeItemProperty(fromIndex);
271 }
272 }
273
274
275
276
277 private void switchItemProperties(Object firstPropertyId, Object secondPropertyId) {
278 Property propertyFirst = getValue().getItemProperty(firstPropertyId);
279 Property propertySecond = getValue().getItemProperty(secondPropertyId);
280
281 try {
282 PropertysetItem storedValues = (PropertysetItem) getValue().clone();
283 if (storedValues != null) {
284 for (Object propertyId : storedValues.getItemPropertyIds()) {
285 getValue().removeItemProperty(propertyId);
286 if (propertyId == firstPropertyId) {
287 getValue().addItemProperty(firstPropertyId, propertySecond);
288 } else if (propertyId == secondPropertyId) {
289 getValue().addItemProperty(secondPropertyId, propertyFirst);
290 } else {
291 getValue().addItemProperty(propertyId, storedValues.getItemProperty(propertyId));
292 }
293 }
294 getPropertyDataSource().setValue(getValue());
295 }
296 } catch (CloneNotSupportedException e) {
297 log.error("Unable to switch properties on MultiField. Unable to clone PropertysetItem.", e);
298 }
299
300 }
301
302 private void onDelete(Component layout, Property<?> propertyReference) {
303 root.removeComponent(layout);
304 Transformer<?> transformer = ((TransformedProperty<?>) getPropertyDataSource()).getTransformer();
305
306
307 Object propertyId = findPropertyId(getValue(), propertyReference);
308
309 if (transformer instanceof MultiTransformer) {
310 ((MultiTransformer) transformer).removeProperty(propertyId);
311 } else {
312 if (propertyId.getClass().isAssignableFrom(Integer.class)) {
313 removeValueProperty((Integer) propertyId);
314 } else {
315 log.error("Property id {} is not an integer and as such property can't be removed", propertyId);
316 }
317 getPropertyDataSource().setValue(getValue());
318 }
319 }
320
321
322
323
324
325 private void onMove(Component layout, Property<?> propertyReference, boolean moveUp) {
326 int currentPosition = root.getComponentIndex(layout);
327 int switchPosition = currentPosition + (moveUp ? -1 : 1);
328
329 Field[] fields = Iterators.toArray(Iterators.filter(Iterators.transform(root.iterator(), new Function<Component, Field>() {
330 @Override
331 public Field apply(Component input) {
332 if (input instanceof HasComponents) {
333 Optional<Component> field = Iterators.tryFind(((HasComponents) input).iterator(), Predicates.instanceOf(Field.class));
334 if (field.isPresent()) {
335 return (Field) field.get();
336 }
337 }
338 return null;
339 }
340 }), Predicates.notNull()), Field.class);
341
342 if (moveUp && currentPosition != 0 || (!moveUp && currentPosition != fields.length - 1)) {
343 Field switchField = fields[switchPosition];
344 Object currentPropertyId = MultiField.this.findPropertyId(getValue(), propertyReference);
345 Object switchPropertyId = MultiField.this.findPropertyId(getValue(), switchField.getPropertyDataSource());
346
347 root.replaceComponent(root.getComponent(currentPosition), root.getComponent(switchPosition));
348 switchItemProperties(currentPropertyId, switchPropertyId);
349 }
350 }
351
352 public boolean isOrderable() {
353 return isOrderable;
354 }
355
356 public void setOrderable(boolean orderable) {
357 isOrderable = orderable;
358 }
359 }