Project

General

Profile

Download (15.6 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
    Boolean valueInitiallyWasNull = null;
51

    
52
    protected boolean isOrderedCollection = false;
53

    
54
    private boolean withEditButton = false;
55

    
56
    private static final int GRID_X_FIELD = 0;
57

    
58
    protected boolean addEmptyRowOnInitContent = true;
59

    
60
    private int GRID_COLS = 2;
61

    
62
    private GridLayout grid = new GridLayout(GRID_COLS, 1);
63

    
64
    private EntityFieldInstantiator<F> entityFieldInstantiator;
65

    
66
    public  ToManyRelatedEntitiesListSelect(Class<V> itemType, Class<F> fieldType, String caption){
67
        this.fieldType = fieldType;
68
        this.itemType = itemType;
69
        setCaption(caption);
70
    }
71

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

    
87
    /**
88
     * @param field
89
     * @return
90
     */
91
    private void addRowAfter(F field) {
92

    
93
        List<V> nestedValues = getValueFromNestedFields();
94

    
95
        if(isOrderedCollection){
96

    
97
        } else {
98

    
99
        }
100

    
101
        Integer row = findRow(field);
102

    
103
        grid.insertRow(row + 1);
104

    
105
        // setting null as value for new rows
106
        // see newFieldInstance() !!!
107
        addNewRow(row + 1, null);
108
        updateValue();
109

    
110
    }
111

    
112
    /**
113
     * @param field
114
     * @return
115
     */
116
    private void removeRow(F field) {
117

    
118
        Integer row = findRow(field);
119
        grid.removeRow(row);
120
        // TODO remove from nested fields
121
        updateValue();
122
        updateButtonStates();
123

    
124
    }
125

    
126
    /**
127
     * @param field
128
     * @return
129
     */
130
    private void moveRowDown(F field) {
131

    
132
        Integer row = findRow(field);
133
        swapRows(row);
134
    }
135

    
136
    /**
137
     * @param field
138
     * @return
139
     */
140
    private void moveRowUp(F field) {
141

    
142
        Integer row = findRow(field);
143
        swapRows(row - 1);
144
    }
145

    
146
    /**
147
     * @param i
148
     */
149
    private void swapRows(int i) {
150
        if(i >= 0 && i + 1 < grid.getRows()){
151
            grid.replaceComponent(grid.getComponent(GRID_X_FIELD, i), grid.getComponent(GRID_X_FIELD, i + 1));
152
            grid.replaceComponent(grid.getComponent(GRID_X_FIELD  + 1 , i), grid.getComponent(GRID_X_FIELD + 1, i + 1));
153
            updateButtonStates();
154
            updateValue();
155
        } else {
156
            throw new RuntimeException("Cannot swap rows out of the grid bounds");
157
        }
158
    }
159

    
160
    /**
161
     * update Value is only called in turn of UI changes like adding, removing, swapping rows
162
     */
163
    private void updateValue() {
164
        List<V> nestedValues = getValueFromNestedFields();
165
        List<V> beanList = getValue();
166
        beanList.clear();
167
        beanList.addAll(nestedValues);
168
        setInternalValue(beanList, false);
169
    }
170

    
171
    /**
172
     * @param field
173
     * @return
174
     */
175
    protected ClickListener newEditButtonClicklistener(F field) {
176
        return null;
177
    }
178

    
179
    /**
180
     * @return
181
     */
182
    protected ClickListener newAddButtonClicklistener(F field) {
183
        return null;
184
    }
185

    
186
    /**
187
     * @return
188
     */
189
    protected ClickListener newRemoveButtonClicklistener(F field) {
190
        return null;
191
    }
192

    
193
    /**
194
     * {@inheritDoc}
195
     */
196
    @Override
197
    protected Component initContent() {
198
        grid.setColumnExpandRatio(0, 1.0f);
199
        // set internal value to null to add an empty row
200

    
201
        if(addEmptyRowOnInitContent){
202
            // add an empty row
203
            setInternalValue(null);
204
        }
205
        return grid;
206
    }
207

    
208
    /**
209
     * {@inheritDoc}
210
     */
211
    @Override
212
    public Class getType() {
213
        return List.class;
214
    }
215

    
216
    /**
217
     * {@inheritDoc}
218
     */
219
    @Override
220
    protected void setInternalValue(List<V> newValue) {
221

    
222
        setInternalValue(newValue, true);
223

    
224
    }
225

    
226
    protected void setInternalValue(List<V> newValue, boolean doUpdateFields) {
227

    
228
        super.setInternalValue(newValue);
229

    
230
        if(valueInitiallyWasNull == null){
231
            valueInitiallyWasNull = newValue == null;
232
        }
233

    
234
        if(newValue != null){
235

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

    
239
        }
240

    
241
        createFieldsForData();
242

    
243
    }
244

    
245
    private void createFieldsForData(){
246

    
247
        grid.removeAllComponents();
248
        grid.setRows(1);
249

    
250
        List<V> data = getValue();
251
        if(data == null || data.isEmpty()){
252
            addNewRow(0, null);
253
        } else {
254
            int row = 0;
255
            for(V val : data){
256
                row = addNewRow(row, val);
257
            }
258
        }
259

    
260
    }
261

    
262
    /**
263
     * Obtains the List of values directly from the nested fields and ignores the
264
     * value of the <code>propertyDataSource</code>. This is useful when the ToManyRelatedEntitiesListSelect
265
     * is operating on a transient field, in which case the property is considered being read only by vaadin
266
     * so that the commit is doing nothing.
267
     *
268
     * See also {@link AbstractCdmEditorPresenter#handleTransientProperties(DTO bean)}
269
     *
270
     * @return
271
     */
272
    public List<V> getValueFromNestedFields() {
273
        List<V> nestedValues = new ArrayList<>();
274
        for(F f : getNestedFields()) {
275
            logger.trace(
276
                    String.format("getValueFromNestedFields() - %s:%s",
277
                       f != null ? f.getClass().getSimpleName() : "null",
278
                       f != null && f.getValue() != null ? f.getValue() : "null"
279
            ));
280
            V value = f.getValue();
281
            if(f != null /*&& value != null*/){
282
                nestedValues.add(f.getValue());
283
            }
284
         }
285
        return nestedValues;
286
    }
287

    
288
    /**
289
     * @param row
290
     * @param val
291
     * @return
292
     */
293
    protected int addNewRow(int row, V val) {
294
        try {
295
            F field = newFieldInstance(val);
296
            field.addValueChangeListener(e -> {
297
                updateValue();
298
            });
299
            Property ds = getPropertyDataSource();
300
            if(ds != null){
301
                Object parentVal = ds.getValue();
302
            }
303
            addStyledComponent(field);
304

    
305
            // important! all fields must be un-buffered
306
            field.setBuffered(false);
307

    
308
            if(getNestedFields().size() == grid.getRows()){
309
                grid.setRows(grid.getRows() + 1);
310
            }
311
            grid.addComponent(field, GRID_X_FIELD, row);
312
            grid.addComponent(buttonGroup(field), 1, row);
313
            updateButtonStates();
314
            nestFieldGroup(field);
315
            row++;
316
        } catch (InstantiationException e) {
317
            // TODO Auto-generated catch block
318
            e.printStackTrace();
319
        } catch (IllegalAccessException e) {
320
            // TODO Auto-generated catch block
321
            e.printStackTrace();
322
        }
323
        return row;
324
    }
325

    
326
    private Component buttonGroup(F field){
327

    
328
        CssLayout buttonGroup = new CssLayout();
329
        Button add = new Button(FontAwesome.PLUS);
330
        add.setDescription("Add item");
331
        add.addClickListener(e -> addRowAfter(field));
332

    
333
        if(withEditButton){
334
            Button edit = new Button(FontAwesome.EDIT);
335
            ClickListener editClickListerner = newEditButtonClicklistener(field);
336
            if(editClickListerner != null){
337
                edit.addClickListener(editClickListerner);
338
            }
339
            buttonGroup.addComponent(edit);
340
            addStyledComponents(edit);
341
        }
342

    
343
        Button remove = new Button(FontAwesome.MINUS);
344
        remove.setDescription("Remove item");
345
        remove.addClickListener(e -> removeRow(field));
346

    
347

    
348
        buttonGroup.addComponent(add);
349
        buttonGroup.addComponent(remove);
350
        addStyledComponents(add, remove);
351
        if(isOrderedCollection){
352
            Button moveUp = new Button(FontAwesome.ARROW_UP);
353
            moveUp.setDescription("Move up");
354
            moveUp.addClickListener(e -> moveRowUp(field));
355
            Button moveDown = new Button(FontAwesome.ARROW_DOWN);
356
            moveDown.addClickListener(e -> moveRowDown(field));
357
            moveDown.setDescription("Move down");
358

    
359
            buttonGroup.addComponents(moveUp, moveDown);
360
            addStyledComponents(moveUp, moveDown);
361
        }
362
        buttonGroup.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
363

    
364
        return buttonGroup;
365
    }
366

    
367
    private void updateButtonStates(){
368

    
369
        int fieldsCount = getNestedFields().size();
370
        for(int row = 0; row < fieldsCount; row++){
371

    
372
            boolean isFirst = row == 0;
373
            boolean isLast = row == fieldsCount - 1;
374

    
375
            CssLayout buttonGroup = (CssLayout) grid.getComponent(GRID_X_FIELD + 1, row);
376

    
377
            // add
378
            buttonGroup.getComponent(0).setEnabled(isLast || isOrderedCollection);
379
            // remove
380
            buttonGroup.getComponent(1).setEnabled(!isFirst);
381
            // up
382
            if(buttonGroup.getComponentCount() > 2){
383
                buttonGroup.getComponent(2).setEnabled(!isFirst);
384
                // down
385
                buttonGroup.getComponent(3).setEnabled(!isLast);
386
            }
387
        }
388
    }
389

    
390

    
391
    protected List<F> getNestedFields(){
392

    
393
        List<F> nestedFields = new ArrayList<>(grid.getRows());
394
        for(int r = 0; r < grid.getRows(); r++){
395
            F f = (F) grid.getComponent(GRID_X_FIELD, r);
396
            if(f == null){
397
                logger.debug(String.format("NULL field at %d,%d", GRID_X_FIELD, r));
398
            } else {
399
                nestedFields.add(f);
400
            }
401
        }
402
        return Collections.unmodifiableList(nestedFields);
403
    }
404

    
405
    /**
406
     *
407
     * @param val
408
     * @return
409
     * @throws InstantiationException
410
     * @throws IllegalAccessException
411
     */
412
    protected F newFieldInstance(V val) throws InstantiationException, IllegalAccessException {
413

    
414
        F field;
415
        if(entityFieldInstantiator != null){
416
            field = entityFieldInstantiator.createNewInstance();
417
        } else {
418
            field = fieldType.newInstance();
419
        }
420

    
421
        field.setWidth(100, Unit.PERCENTAGE);
422
        field.setValue(val);
423

    
424
        // TODO
425
        // when passing null as value the field must take care of creating a new
426
        // instance by overriding setValue() in future we could improve this by passing a
427
        // NewInstanceFactory to this class
428
        return field;
429
    }
430

    
431
    /**
432
     * Handle the data binding of the sub fields. Sub-fields can either be composite editor fields
433
     * or 'simple' fields, usually select fields.
434
     * <p>
435
     * Composite editor fields allow editing the nested bean Items and must implement the
436
     * {@link NestedFieldGroup} interface. Simple fields are only instantiated in
437
     * {@link #newFieldInstance(Object)} where the value of the field is set. No further binding is needed
438
     * for these 'simple' fields.
439
     *
440
     * @param field
441
     */
442
    protected void nestFieldGroup(F field) {
443
        if(NestedFieldGroup.class.isAssignableFrom(fieldType) && parentFieldGroup != null){
444
            ((NestedFieldGroup)field).registerParentFieldGroup(parentFieldGroup);
445
        }
446
    }
447

    
448
    /**
449
     * {@inheritDoc}
450
     * <p>
451
     * However, this class has no local fieldGroup but must delegate to the nested NestedFieldGroup
452
     * if there are any. This happens in {@link #nestFieldGroup(AbstractField)}.
453
     * <p>
454
     */
455
    @Override
456
    public FieldGroup getFieldGroup() {
457
        return null;
458
    }
459

    
460
    /**
461
     * This ToMany-CompositeCustomField has no own fields and this no local fieldGroup (see {@link #getFieldGroup()})
462
     * which allow changing data. Editing of the list items is delegated to
463
     * a list of sub-fields which are responsible for editing and committing the changes.
464
     * Therefore the <code>parentFieldGroup</code> is only stored in a local field so that it can
465
     * be passed to per item fields in {@link #nestFieldGroup}
466
     *
467
     * {@inheritDoc}
468
     */
469
    @Override
470
    public void registerParentFieldGroup(FieldGroup parent) {
471
        parentFieldGroup = parent;
472
    }
473

    
474
    /**
475
     * {@inheritDoc}
476
     */
477
    @Override
478
    public void commit() throws SourceException, InvalidValueException {
479

    
480
        List<F> nestedFields = getNestedFields();
481
        for(F f : nestedFields){
482
            f.commit();
483

    
484
        }
485
        /*
486
        List<V> list = (List<V>) getPropertyDataSource().getValue();
487

    
488
        Person p = Person.NewInstance();
489
        p.setTitleCache("Hacky", true);
490
        list.add((V) p);
491

    
492
        List<V> clonedList = new ArrayList<>(list);
493
        list.clear();
494
        for(V value : clonedList){
495
            if(value != null){
496
                list.add(value);
497
            }
498
        }
499
        //
500
        // calling super.commit() is useless if operating on a transient property!!
501
        super.commit();
502
        if(getValue().isEmpty() && valueInitiallyWasNull){
503
            setPropertyDataSource(null);
504
        }
505
         */
506
    }
507

    
508
    /**
509
     * {@inheritDoc}
510
     */
511
    @Override
512
    public void setWidth(String width) {
513
        super.setWidth(width);
514
        grid.setWidth(width);
515
    }
516

    
517
    @Override
518
    public void setWidth(float width, Unit unit){
519
        super.setWidth(width, unit);
520
        if(grid != null){
521
            grid.setWidth(width, unit);
522
        }
523
    }
524

    
525
    /**
526
     * {@inheritDoc}
527
     */
528
    @Override
529
    protected void addDefaultStyles() {
530
        // no default styles
531
    }
532

    
533
    public void withEditButton(boolean withEditButton){
534
        this.withEditButton = withEditButton;
535
    }
536

    
537
    /**
538
     * {@inheritDoc}
539
     */
540
    @Override
541
    public boolean hasNullContent() {
542

    
543
        for(Field f : getNestedFields()){
544
            if(f instanceof CompositeCustomField){
545
                if(!((CompositeCustomField)f).hasNullContent()){
546
                    return false;
547
                }
548
            }
549
        }
550
        return true;
551
    }
552

    
553
    /**
554
     * @return the enityFieldInstantiator
555
     */
556
    public EntityFieldInstantiator<F> getEntityFieldInstantiator() {
557
        return entityFieldInstantiator;
558
    }
559

    
560
    /**
561
     * @param enityFieldInstantiator the enityFieldInstantiator to set
562
     */
563
    public void setEntityFieldInstantiator(EntityFieldInstantiator<F> entityFieldInstantiator) {
564
        this.entityFieldInstantiator = entityFieldInstantiator;
565
    }
566

    
567

    
568
}
(8-8/11)