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.LinkedList;
14
import java.util.List;
15

    
16
import org.apache.log4j.Logger;
17

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

    
30
import eu.etaxonomy.vaadin.mvp.AbstractCdmEditorPresenter;
31

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

    
42
    private static final long serialVersionUID = 4670707714503199599L;
43

    
44
    private static final Logger logger = Logger.getLogger(ToManyRelatedEntitiesListSelect.class);
45

    
46
    protected Class<F> fieldType;
47

    
48
    protected Class<V> itemType;
49

    
50
    private FieldGroup parentFieldGroup = 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
    //NOTE: Managing the item
61
    //      IDs makes BeanContainer more complex to use, but it is necessary in some cases where the
62
    //      equals() or hashCode() methods have been re-implemented in the bean.
63
    //      TODO CdmBase has a re-implemented equals method, do we need to use the BeanContainer instead?
64
    private BeanItemContainer<V> beans;
65

    
66
   //private LinkedList<V> itemList = new LinkedList<>();
67

    
68
    private int GRID_COLS = 2;
69

    
70
    private GridLayout grid = new GridLayout(GRID_COLS, 1);
71

    
72
    public  ToManyRelatedEntitiesListSelect(Class<V> itemType, Class<F> fieldType, String caption){
73
        this.fieldType = fieldType;
74
        this.itemType = itemType;
75
        setCaption(caption);
76
        beans = new BeanItemContainer<V>(itemType);
77

    
78
    }
79

    
80
    /**
81
     * @param field
82
     * @return
83
     */
84
    protected Integer findRow(F field) {
85
        Integer row = null;
86
        for(int r = 0; r < grid.getRows(); r++){
87
            if(grid.getComponent(GRID_X_FIELD, r).equals(field)){
88
                row = r;
89
                break;
90
            }
91
        }
92
        return row;
93
    }
94

    
95
    /**
96
     * @param field
97
     * @return
98
     */
99
    private void addRowAfter(F field) {
100

    
101
        Integer row = findRow(field);
102

    
103
        grid.insertRow(row + 1);
104

    
105
        addNewRow(row + 1, null);
106
        updateValue();
107

    
108
    }
109

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

    
116
        Integer row = findRow(field);
117
        grid.removeRow(row);
118
        updateValue();
119
        updateButtonStates();
120

    
121
    }
122

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

    
129
        Integer row = findRow(field);
130
        swapRows(row);
131
    }
132

    
133
    /**
134
     * @param field
135
     * @return
136
     */
137
    private void moveRowUp(F field) {
138

    
139
        Integer row = findRow(field);
140
        swapRows(row - 1);
141
    }
142

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

    
157
    /**
158
     *
159
     */
160
    protected void updateValue() {
161
        try {
162
            setValue(getValueFromNestedFields());
163
        } catch (ReadOnlyException e){
164
            logger.debug("datasource is readonly, only internal value was updated");
165
        }
166
    }
167

    
168
    /**
169
     * @param field
170
     * @return
171
     */
172
    protected ClickListener newEditButtonClicklistener(F field) {
173
        return null;
174
    }
175

    
176
    /**
177
     * @return
178
     */
179
    protected ClickListener newAddButtonClicklistener(F field) {
180
        return null;
181
    }
182

    
183
    /**
184
     * @return
185
     */
186
    protected ClickListener newRemoveButtonClicklistener(F field) {
187
        return null;
188
    }
189

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

    
198
        if(addEmptyRowOnInitContent){
199
            // add an empty row
200
            setInternalValue(null);
201
        }
202
        return grid;
203
    }
204

    
205
    /**
206
     * {@inheritDoc}
207
     */
208
    @Override
209
    public Class getType() {
210
        return List.class;
211
    }
212

    
213
    /**
214
     * {@inheritDoc}
215
     */
216
    @Override
217
    protected void setInternalValue(List<V> newValue) {
218

    
219
         grid.removeAllComponents();
220
         grid.setRows(1);
221

    
222
        if(newValue != null){
223
            // FIMXE is it really needed to backup as linked list?
224
            LinkedList<V> linkedList;
225
            if(newValue instanceof LinkedList){
226
                linkedList = (LinkedList<V>) newValue;
227
            } else {
228
                linkedList = new LinkedList<>(newValue);
229
            }
230
            super.setInternalValue(linkedList);
231

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

    
235
            //FIXME is beans really used?
236
            beans.addAll(linkedList);
237

    
238
            int row = 0;
239
            if(newValue.size() > 0){
240
                for(V val : linkedList){
241
                    row = addNewRow(row, val);
242
                }
243
            }
244
        }
245

    
246
        if(newValue == null || newValue.isEmpty()) {
247
            // add an empty row
248
            addNewRow(0, null);
249
        }
250
    }
251

    
252
    /**
253
     * @param row
254
     * @param val
255
     * @return
256
     */
257
    protected int addNewRow(int row, V val) {
258
        try {
259
            F field = newFieldInstance(val);
260
            addStyledComponent(field);
261

    
262
            // important! all fields must be un-buffered
263
            field.setBuffered(false);
264

    
265
            if(getNestedFields().size() == grid.getRows()){
266
                grid.setRows(grid.getRows() + 1);
267
            }
268
            grid.addComponent(field, GRID_X_FIELD, row);
269
            grid.addComponent(buttonGroup(field), 1, row);
270
            updateButtonStates();
271
            nestFieldGroup(field);
272
            row++;
273
        } catch (InstantiationException e) {
274
            // TODO Auto-generated catch block
275
            e.printStackTrace();
276
        } catch (IllegalAccessException e) {
277
            // TODO Auto-generated catch block
278
            e.printStackTrace();
279
        }
280
        return row;
281
    }
282

    
283
    private Component buttonGroup(F field){
284

    
285
        CssLayout buttonGroup = new CssLayout();
286
        Button add = new Button(FontAwesome.PLUS);
287
        add.setDescription("Add item");
288
        add.addClickListener(e -> addRowAfter(field));
289

    
290
        if(withEditButton){
291
            Button edit = new Button(FontAwesome.EDIT);
292
            ClickListener editClickListerner = newEditButtonClicklistener(field);
293
            if(editClickListerner != null){
294
                edit.addClickListener(editClickListerner);
295
            }
296
            buttonGroup.addComponent(edit);
297
            addStyledComponents(edit);
298
        }
299

    
300
        Button remove = new Button(FontAwesome.MINUS);
301
        remove.setDescription("Remove item");
302
        remove.addClickListener(e -> removeRow(field));
303

    
304

    
305
        buttonGroup.addComponent(add);
306
        buttonGroup.addComponent(remove);
307
        addStyledComponents(add, remove);
308
        if(isOrderedCollection){
309
            Button moveUp = new Button(FontAwesome.ARROW_UP);
310
            moveUp.setDescription("Move up");
311
            moveUp.addClickListener(e -> moveRowUp(field));
312
            Button moveDown = new Button(FontAwesome.ARROW_DOWN);
313
            moveDown.addClickListener(e -> moveRowDown(field));
314
            moveDown.setDescription("Move down");
315

    
316
            buttonGroup.addComponents(moveUp, moveDown);
317
            addStyledComponents(moveUp, moveDown);
318
        }
319
        buttonGroup.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
320

    
321
        return buttonGroup;
322
    }
323

    
324
    private void updateButtonStates(){
325

    
326
        int fieldsCount = getNestedFields().size();
327
        for(int row = 0; row < fieldsCount; row++){
328

    
329
            boolean isFirst = row == 0;
330
            boolean isLast = row == fieldsCount - 1;
331

    
332
            CssLayout buttonGroup = (CssLayout) grid.getComponent(GRID_X_FIELD + 1, row);
333

    
334
            // add
335
            buttonGroup.getComponent(0).setEnabled(isLast || isOrderedCollection);
336
            // remove
337
            buttonGroup.getComponent(1).setEnabled(!isFirst);
338
            // up
339
            if(buttonGroup.getComponentCount() > 2){
340
                buttonGroup.getComponent(2).setEnabled(!isFirst);
341
                // down
342
                buttonGroup.getComponent(3).setEnabled(!isLast);
343
            }
344
        }
345
    }
346

    
347

    
348
    protected List<F> getNestedFields(){
349
        List<F> nestedFields = new ArrayList<>(grid.getRows());
350
        for(int r = 0; r < grid.getRows(); r++){
351
            F f = (F) grid.getComponent(GRID_X_FIELD, r);
352
            if(f == null){
353
                logger.debug(String.format("NULL field at %d,%d", GRID_X_FIELD, r));
354
            } else {
355
                nestedFields.add(f);
356
            }
357
        }
358
        return Collections.unmodifiableList(nestedFields);
359
    }
360

    
361
    /**
362
     *
363
     * @param val
364
     * @return
365
     * @throws InstantiationException
366
     * @throws IllegalAccessException
367
     */
368
    protected F newFieldInstance(V val) throws InstantiationException, IllegalAccessException {
369
        F field = fieldType.newInstance();
370
        field.setWidth(100, Unit.PERCENTAGE);
371
        field.setValue(val);
372
        return field;
373
    }
374

    
375
    /**
376
     * Handle the data binding of the sub fields. Sub-fields can either be composite editor fields
377
     * or 'simple' fields, usually select fields.
378
     * <p>
379
     * Composite editor fields allow editing the nested bean Items and must implement the
380
     * {@link NestedFieldGroup} interface. Simple fields are only instantiated in
381
     * {@link #newFieldInstance(Object)} where the value of the field is set. No further binding is needed
382
     * for these 'simple' fields.
383
     *
384
     * @param field
385
     */
386
    protected void nestFieldGroup(F field) {
387
        if(NestedFieldGroup.class.isAssignableFrom(fieldType) && parentFieldGroup != null){
388
            ((NestedFieldGroup)field).registerParentFieldGroup(parentFieldGroup);
389
        }
390
    }
391

    
392
    /**
393
     * {@inheritDoc}
394
     * <p>
395
     * However, this class has no local fieldGroup but must delegate to the nested NestedFieldGroup
396
     * if there are any. This happens in {@link #nestFieldGroup(AbstractField)}.
397
     * <p>
398
     */
399
    @Override
400
    public FieldGroup getFieldGroup() {
401
        return null;
402
    }
403

    
404
    /**
405
     * This ToMany-CompositeCustomField has no own fields and this no local fieldGroup (see {@link #getFieldGroup()})
406
     * which allow changing data. Editing of the list items is delegated to
407
     * a list of sub-fields which are responsible for editing and committing the changes.
408
     * Therefore the <code>parentFieldGroup</code> is only stored in a local field so that it can
409
     * be passed to per item fields in {@link #nestFieldGroup}
410
     *
411
     * {@inheritDoc}
412
     */
413
    @Override
414
    public void registerParentFieldGroup(FieldGroup parent) {
415
        parentFieldGroup = parent;
416
    }
417

    
418
    /**
419
     * {@inheritDoc}
420
     */
421
    @Override
422
    public void commit() throws SourceException, InvalidValueException {
423
        getNestedFields().forEach(f -> f.commit());
424
        // calling super.commit() is useless if operating on a transient property!!
425
        super.commit();
426
    }
427

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

    
452
    /**
453
     * {@inheritDoc}
454
     */
455
    @Override
456
    public void setWidth(String width) {
457
        super.setWidth(width);
458
        grid.setWidth(width);
459
    }
460

    
461
    @Override
462
    public void setWidth(float width, Unit unit){
463
        super.setWidth(width, unit);
464
        if(grid != null){
465
            grid.setWidth(width, unit);
466
        }
467
    }
468

    
469
    /**
470
     * {@inheritDoc}
471
     */
472
    @Override
473
    protected void addDefaultStyles() {
474
        // no default styles
475
    }
476

    
477
    public void withEditButton(boolean withEditButton){
478
        this.withEditButton = withEditButton;
479
    }
480

    
481
}
(7-7/10)