Project

General

Profile

Download (13.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2017 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.vaadin.component;
10

    
11
import java.util.ArrayList;
12
import java.util.Collections;
13
import java.util.List;
14

    
15
import org.apache.log4j.Logger;
16

    
17
import com.vaadin.data.Property;
18
import com.vaadin.data.Validator.InvalidValueException;
19
import com.vaadin.data.fieldgroup.FieldGroup;
20
import com.vaadin.server.FontAwesome;
21
import com.vaadin.ui.AbstractField;
22
import com.vaadin.ui.Button;
23
import com.vaadin.ui.Button.ClickListener;
24
import com.vaadin.ui.Component;
25
import com.vaadin.ui.CssLayout;
26
import com.vaadin.ui.Field;
27
import com.vaadin.ui.GridLayout;
28
import com.vaadin.ui.themes.ValoTheme;
29

    
30
/**
31
 * Manages the a collection of items internally as LinkedList<V>. If the Collection to operate on is a Set a Converter must be
32
 * set. Internally used fields are used in un-buffered mode.
33
 *
34
 * @author a.kohlbecker
35
 * @since May 11, 2017
36
 *
37
 */
38
public class ToManyRelatedEntitiesListSelect<V extends Object, F extends AbstractField<V>>  extends CompositeCustomField<List<V>> {
39

    
40
    private static final long serialVersionUID = 4670707714503199599L;
41

    
42
    private static final Logger logger = Logger.getLogger(ToManyRelatedEntitiesListSelect.class);
43

    
44
    protected Class<F> fieldType;
45

    
46
    protected Class<V> itemType;
47

    
48
    private FieldGroup parentFieldGroup = null;
49

    
50
    protected boolean isOrderedCollection = false;
51

    
52
    private boolean withEditButton = false;
53

    
54
    private static final int GRID_X_FIELD = 0;
55

    
56
    protected boolean addEmptyRowOnInitContent = true;
57

    
58
    private int GRID_COLS = 2;
59

    
60
    private GridLayout grid = new GridLayout(GRID_COLS, 1);
61

    
62
    public  ToManyRelatedEntitiesListSelect(Class<V> itemType, Class<F> fieldType, String caption){
63
        this.fieldType = fieldType;
64
        this.itemType = itemType;
65
        setCaption(caption);
66
    }
67

    
68
    /**
69
     * @param field
70
     * @return
71
     */
72
    protected Integer findRow(F field) {
73
        Integer row = null;
74
        for(int r = 0; r < grid.getRows(); r++){
75
            if(grid.getComponent(GRID_X_FIELD, r).equals(field)){
76
                row = r;
77
                break;
78
            }
79
        }
80
        return row;
81
    }
82

    
83
    /**
84
     * @param field
85
     * @return
86
     */
87
    private void addRowAfter(F field) {
88

    
89
        Integer row = findRow(field);
90

    
91
        grid.insertRow(row + 1);
92

    
93
        // setting null as value for new rows
94
        // see newFieldInstance() !!!
95
        addNewRow(row + 1, null);
96
        updateValue();
97

    
98
    }
99

    
100
    /**
101
     * @param field
102
     * @return
103
     */
104
    private void removeRow(F field) {
105

    
106
        Integer row = findRow(field);
107
        grid.removeRow(row);
108
        // TODO remove from nested fields
109
        updateValue();
110
        updateButtonStates();
111

    
112
    }
113

    
114
    /**
115
     * @param field
116
     * @return
117
     */
118
    private void moveRowDown(F field) {
119

    
120
        Integer row = findRow(field);
121
        swapRows(row);
122
    }
123

    
124
    /**
125
     * @param field
126
     * @return
127
     */
128
    private void moveRowUp(F field) {
129

    
130
        Integer row = findRow(field);
131
        swapRows(row - 1);
132
    }
133

    
134
    /**
135
     * @param i
136
     */
137
    private void swapRows(int i) {
138
        if(i >= 0 && i + 1 < grid.getRows()){
139
            grid.replaceComponent(grid.getComponent(GRID_X_FIELD, i), grid.getComponent(GRID_X_FIELD, i + 1));
140
            grid.replaceComponent(grid.getComponent(GRID_X_FIELD  + 1 , i), grid.getComponent(GRID_X_FIELD + 1, i + 1));
141
            updateButtonStates();
142
            updateValue();
143
        } else {
144
            throw new RuntimeException("Cannot swap rows out of the grid bounds");
145
        }
146
    }
147

    
148
    /**
149
     *
150
     */
151
    protected void updateValue() {
152
        List<V> nestedValues = getValueFromNestedFields();
153
        List<V> beanList = getValue();
154
        beanList.clear();
155
        beanList.addAll(nestedValues);
156
        setInternalValue(beanList);
157
    }
158

    
159
    /**
160
     * @param field
161
     * @return
162
     */
163
    protected ClickListener newEditButtonClicklistener(F field) {
164
        return null;
165
    }
166

    
167
    /**
168
     * @return
169
     */
170
    protected ClickListener newAddButtonClicklistener(F field) {
171
        return null;
172
    }
173

    
174
    /**
175
     * @return
176
     */
177
    protected ClickListener newRemoveButtonClicklistener(F field) {
178
        return null;
179
    }
180

    
181
    /**
182
     * {@inheritDoc}
183
     */
184
    @Override
185
    protected Component initContent() {
186
        grid.setColumnExpandRatio(0, 1.0f);
187
        // set internal value to null to add an empty row
188

    
189
        if(addEmptyRowOnInitContent){
190
            // add an empty row
191
            setInternalValue(null);
192
        }
193
        return grid;
194
    }
195

    
196
    /**
197
     * {@inheritDoc}
198
     */
199
    @Override
200
    public Class getType() {
201
        return List.class;
202
    }
203

    
204
    /**
205
     * {@inheritDoc}
206
     */
207
    @Override
208
    protected void setInternalValue(List<V> newValue) {
209

    
210
         grid.removeAllComponents();
211
         grid.setRows(1);
212

    
213
        if(newValue != null){
214
            super.setInternalValue(newValue);
215

    
216
            // newValue is already converted, need to use the original value from the data source
217
            isOrderedCollection = List.class.isAssignableFrom(getPropertyDataSource().getValue().getClass());
218

    
219
            int row = 0;
220
            if(newValue.size() > 0){
221
                for(V val : newValue){
222
                    row = addNewRow(row, val);
223
                }
224
            }
225
        }
226

    
227
        if(newValue == null || newValue.isEmpty()) {
228
            // add an empty row
229
            addNewRow(0, null);
230
        }
231
    }
232

    
233
    /**
234
     * @param row
235
     * @param val
236
     * @return
237
     */
238
    protected int addNewRow(int row, V val) {
239
        try {
240
            F field = newFieldInstance(val);
241
            Property ds = getPropertyDataSource();
242
            if(ds != null){
243
                Object parentVal = ds.getValue();
244
            }
245
            addStyledComponent(field);
246

    
247
            // important! all fields must be un-buffered
248
            field.setBuffered(false);
249

    
250
            if(getNestedFields().size() == grid.getRows()){
251
                grid.setRows(grid.getRows() + 1);
252
            }
253
            grid.addComponent(field, GRID_X_FIELD, row);
254
            grid.addComponent(buttonGroup(field), 1, row);
255
            updateButtonStates();
256
            nestFieldGroup(field);
257
            row++;
258
        } catch (InstantiationException e) {
259
            // TODO Auto-generated catch block
260
            e.printStackTrace();
261
        } catch (IllegalAccessException e) {
262
            // TODO Auto-generated catch block
263
            e.printStackTrace();
264
        }
265
        return row;
266
    }
267

    
268
    private Component buttonGroup(F field){
269

    
270
        CssLayout buttonGroup = new CssLayout();
271
        Button add = new Button(FontAwesome.PLUS);
272
        add.setDescription("Add item");
273
        add.addClickListener(e -> addRowAfter(field));
274

    
275
        if(withEditButton){
276
            Button edit = new Button(FontAwesome.EDIT);
277
            ClickListener editClickListerner = newEditButtonClicklistener(field);
278
            if(editClickListerner != null){
279
                edit.addClickListener(editClickListerner);
280
            }
281
            buttonGroup.addComponent(edit);
282
            addStyledComponents(edit);
283
        }
284

    
285
        Button remove = new Button(FontAwesome.MINUS);
286
        remove.setDescription("Remove item");
287
        remove.addClickListener(e -> removeRow(field));
288

    
289

    
290
        buttonGroup.addComponent(add);
291
        buttonGroup.addComponent(remove);
292
        addStyledComponents(add, remove);
293
        if(isOrderedCollection){
294
            Button moveUp = new Button(FontAwesome.ARROW_UP);
295
            moveUp.setDescription("Move up");
296
            moveUp.addClickListener(e -> moveRowUp(field));
297
            Button moveDown = new Button(FontAwesome.ARROW_DOWN);
298
            moveDown.addClickListener(e -> moveRowDown(field));
299
            moveDown.setDescription("Move down");
300

    
301
            buttonGroup.addComponents(moveUp, moveDown);
302
            addStyledComponents(moveUp, moveDown);
303
        }
304
        buttonGroup.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
305

    
306
        return buttonGroup;
307
    }
308

    
309
    private void updateButtonStates(){
310

    
311
        int fieldsCount = getNestedFields().size();
312
        for(int row = 0; row < fieldsCount; row++){
313

    
314
            boolean isFirst = row == 0;
315
            boolean isLast = row == fieldsCount - 1;
316

    
317
            CssLayout buttonGroup = (CssLayout) grid.getComponent(GRID_X_FIELD + 1, row);
318

    
319
            // add
320
            buttonGroup.getComponent(0).setEnabled(isLast || isOrderedCollection);
321
            // remove
322
            buttonGroup.getComponent(1).setEnabled(!isFirst);
323
            // up
324
            if(buttonGroup.getComponentCount() > 2){
325
                buttonGroup.getComponent(2).setEnabled(!isFirst);
326
                // down
327
                buttonGroup.getComponent(3).setEnabled(!isLast);
328
            }
329
        }
330
    }
331

    
332

    
333
    protected List<F> getNestedFields(){
334
        List<F> nestedFields = new ArrayList<>(grid.getRows());
335
        for(int r = 0; r < grid.getRows(); r++){
336
            F f = (F) grid.getComponent(GRID_X_FIELD, r);
337
            if(f == null){
338
                logger.debug(String.format("NULL field at %d,%d", GRID_X_FIELD, r));
339
            } else {
340
                nestedFields.add(f);
341
            }
342
        }
343
        return Collections.unmodifiableList(nestedFields);
344
    }
345

    
346
    /**
347
     *
348
     * @param val
349
     * @return
350
     * @throws InstantiationException
351
     * @throws IllegalAccessException
352
     */
353
    protected F newFieldInstance(V val) throws InstantiationException, IllegalAccessException {
354
        F field = fieldType.newInstance();
355
        field.setWidth(100, Unit.PERCENTAGE);
356
        field.setValue(val);
357
        // TODO
358
        // when passing null as value the field must take care of creating a new
359
        // instance by overriding setValue() in future we could improve this by passing a
360
        // NewInstanceFactory to this class
361
        return field;
362
    }
363

    
364
    /**
365
     * Handle the data binding of the sub fields. Sub-fields can either be composite editor fields
366
     * or 'simple' fields, usually select fields.
367
     * <p>
368
     * Composite editor fields allow editing the nested bean Items and must implement the
369
     * {@link NestedFieldGroup} interface. Simple fields are only instantiated in
370
     * {@link #newFieldInstance(Object)} where the value of the field is set. No further binding is needed
371
     * for these 'simple' fields.
372
     *
373
     * @param field
374
     */
375
    protected void nestFieldGroup(F field) {
376
        if(NestedFieldGroup.class.isAssignableFrom(fieldType) && parentFieldGroup != null){
377
            ((NestedFieldGroup)field).registerParentFieldGroup(parentFieldGroup);
378
        }
379
    }
380

    
381
    /**
382
     * {@inheritDoc}
383
     * <p>
384
     * However, this class has no local fieldGroup but must delegate to the nested NestedFieldGroup
385
     * if there are any. This happens in {@link #nestFieldGroup(AbstractField)}.
386
     * <p>
387
     */
388
    @Override
389
    public FieldGroup getFieldGroup() {
390
        return null;
391
    }
392

    
393
    /**
394
     * This ToMany-CompositeCustomField has no own fields and this no local fieldGroup (see {@link #getFieldGroup()})
395
     * which allow changing data. Editing of the list items is delegated to
396
     * a list of sub-fields which are responsible for editing and committing the changes.
397
     * Therefore the <code>parentFieldGroup</code> is only stored in a local field so that it can
398
     * be passed to per item fields in {@link #nestFieldGroup}
399
     *
400
     * {@inheritDoc}
401
     */
402
    @Override
403
    public void registerParentFieldGroup(FieldGroup parent) {
404
        parentFieldGroup = parent;
405
    }
406

    
407
    /**
408
     * {@inheritDoc}
409
     */
410
    @Override
411
    public void commit() throws SourceException, InvalidValueException {
412

    
413
        List<F> nestedFields = getNestedFields();
414
        for(F f : nestedFields){
415
            f.commit();
416

    
417
        }
418
        // calling super.commit() is useless if operating on a transient property!!
419
        super.commit();
420
    }
421

    
422
    /**
423
     * Obtains the List of values directly from the nested fields and ignores the
424
     * value of the <code>propertyDataSource</code>. This is useful when the ToManyRelatedEntitiesListSelect
425
     * is operating on a transient field, in which case the property is considered being read only by vaadin
426
     * so that the commit is doing nothing.
427
     *
428
     * See also {@link AbstractCdmEditorPresenter#handleTransientProperties(DTO bean)}
429
     *
430
     * @return
431
     */
432
    public List<V> getValueFromNestedFields() {
433
        List<V> nestedValues = new ArrayList<>();
434
        for(F f : getNestedFields()) {
435
            logger.trace(
436
                    String.format("getValueFromNestedFields() - %s:%s",
437
                       f != null ? f.getClass().getSimpleName() : "null",
438
                       f != null && f.getValue() != null ? f.getValue() : "null"
439
            ));
440
            V value = f.getValue();
441
            if(f != null && value != null){
442
                nestedValues.add(f.getValue());
443
            }
444
         }
445
        return nestedValues;
446
    }
447

    
448
    /**
449
     * {@inheritDoc}
450
     */
451
    @Override
452
    public void setWidth(String width) {
453
        super.setWidth(width);
454
        grid.setWidth(width);
455
    }
456

    
457
    @Override
458
    public void setWidth(float width, Unit unit){
459
        super.setWidth(width, unit);
460
        if(grid != null){
461
            grid.setWidth(width, unit);
462
        }
463
    }
464

    
465
    /**
466
     * {@inheritDoc}
467
     */
468
    @Override
469
    protected void addDefaultStyles() {
470
        // no default styles
471
    }
472

    
473
    public void withEditButton(boolean withEditButton){
474
        this.withEditButton = withEditButton;
475
    }
476

    
477
    /**
478
     * {@inheritDoc}
479
     */
480
    @Override
481
    public boolean hasNullContent() {
482

    
483
        for(Field f : getNestedFields()){
484
            if(f instanceof CompositeCustomField){
485
                if(!((CompositeCustomField)f).hasNullContent()){
486
                    return false;
487
                }
488
            }
489
        }
490
        return true;
491
    }
492

    
493

    
494

    
495
}
(7-7/10)