Project

General

Profile

Download (18.4 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.mvp;
10

    
11
import java.util.Map;
12

    
13
import org.apache.log4j.Logger;
14

    
15
import com.vaadin.data.Validator.InvalidValueException;
16
import com.vaadin.data.fieldgroup.BeanFieldGroup;
17
import com.vaadin.data.fieldgroup.FieldGroup.CommitEvent;
18
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
19
import com.vaadin.data.fieldgroup.FieldGroup.CommitHandler;
20
import com.vaadin.data.fieldgroup.FieldGroup.FieldGroupInvalidValueException;
21
import com.vaadin.server.AbstractErrorMessage.ContentMode;
22
import com.vaadin.server.ErrorMessage.ErrorLevel;
23
import com.vaadin.server.FontAwesome;
24
import com.vaadin.server.UserError;
25
import com.vaadin.ui.AbstractField;
26
import com.vaadin.ui.AbstractLayout;
27
import com.vaadin.ui.AbstractOrderedLayout;
28
import com.vaadin.ui.Alignment;
29
import com.vaadin.ui.Button;
30
import com.vaadin.ui.CheckBox;
31
import com.vaadin.ui.Component;
32
import com.vaadin.ui.CssLayout;
33
import com.vaadin.ui.Field;
34
import com.vaadin.ui.GridLayout;
35
import com.vaadin.ui.GridLayout.OutOfBoundsException;
36
import com.vaadin.ui.GridLayout.OverlapsException;
37
import com.vaadin.ui.HorizontalLayout;
38
import com.vaadin.ui.Layout;
39
import com.vaadin.ui.Notification;
40
import com.vaadin.ui.Notification.Type;
41
import com.vaadin.ui.PopupDateField;
42
import com.vaadin.ui.TextField;
43
import com.vaadin.ui.VerticalLayout;
44
import com.vaadin.ui.themes.ValoTheme;
45

    
46
import eu.etaxonomy.cdm.database.PermissionDeniedException;
47
import eu.etaxonomy.vaadin.component.NestedFieldGroup;
48
import eu.etaxonomy.vaadin.component.SwitchableTextField;
49
import eu.etaxonomy.vaadin.mvp.event.EditorDeleteEvent;
50
import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
51
import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
52
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
53
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
54

    
55
/**
56
 * @author a.kohlbecker
57
 * @since Apr 5, 2017
58
 *
59
 */
60
public abstract class AbstractPopupEditor<DTO extends Object, P extends AbstractEditorPresenter<DTO, ? extends ApplicationView>>
61
    extends AbstractPopupView<P> {
62

    
63
    private BeanFieldGroup<DTO> fieldGroup;
64

    
65
    private VerticalLayout mainLayout;
66

    
67
    private Layout fieldLayout;
68

    
69
    private HorizontalLayout buttonLayout;
70

    
71
    private Button save;
72

    
73
    private Button cancel;
74

    
75
    private Button delete;
76

    
77
    private CssLayout toolBar = new CssLayout();
78

    
79
    private CssLayout toolBarButtonGroup = new CssLayout();
80

    
81
    private GridLayout _gridLayoutCache;
82

    
83
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
84

    
85
        setWidthUndefined();
86

    
87
        mainLayout = new VerticalLayout();
88
        mainLayout.setWidthUndefined();
89

    
90
        fieldGroup = new BeanFieldGroup<>(dtoType);
91
        fieldGroup.addCommitHandler(new SaveHandler());
92

    
93
        setCompositionRoot(mainLayout);
94

    
95
        toolBar.addStyleName(ValoTheme.WINDOW_TOP_TOOLBAR);
96
        toolBar.setWidth(100, Unit.PERCENTAGE);
97
        toolBarButtonGroup.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
98
        toolBarButtonGroup.setWidthUndefined();
99
        toolBar.addComponent(toolBarButtonGroup);
100
        toolBar.setVisible(false);
101

    
102
        fieldLayout = layout;
103
        fieldLayout.setWidthUndefined();
104
        if(fieldLayout instanceof AbstractOrderedLayout){
105
            ((AbstractOrderedLayout)fieldLayout).setSpacing(true);
106
        }
107

    
108
        buttonLayout = new HorizontalLayout();
109
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
110
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
111
        buttonLayout.setSpacing(true);
112

    
113
        save = new Button("Save", FontAwesome.SAVE);
114
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
115
        save.addClickListener(e -> save());
116

    
117
        cancel = new Button("Cancel", FontAwesome.TRASH);
118
        cancel.addClickListener(e -> cancel());
119

    
120
        delete = new Button("Delete", FontAwesome.REMOVE);
121
        delete.setStyleName(ValoTheme.BUTTON_DANGER);
122
        delete.addClickListener(e -> delete());
123

    
124
        buttonLayout.addComponents(delete, save, cancel);
125
        buttonLayout.setExpandRatio(delete, 1);
126
        buttonLayout.setComponentAlignment(delete, Alignment.TOP_RIGHT);
127
        buttonLayout.setComponentAlignment(save, Alignment.TOP_RIGHT);
128
        buttonLayout.setComponentAlignment(cancel, Alignment.TOP_RIGHT);
129

    
130
        mainLayout.addComponents(toolBar, fieldLayout, buttonLayout);
131
        mainLayout.setComponentAlignment(toolBar, Alignment.TOP_RIGHT);
132
    }
133

    
134
    protected VerticalLayout getMainLayout() {
135
        return mainLayout;
136
    }
137

    
138
    protected Layout getFieldLayout() {
139
        return fieldLayout;
140
    }
141

    
142
    /**
143
     * @return
144
     */
145
    private GridLayout gridLayout() {
146
        if(_gridLayoutCache == null){
147
            if(fieldLayout instanceof GridLayout){
148
                _gridLayoutCache = (GridLayout)fieldLayout;
149
            } else {
150
                throw new RuntimeException("The fieldlayout of this editor is not a GridLayout");
151
            }
152
        }
153
        return _gridLayoutCache;
154
    }
155

    
156
    @Override
157
    public void setReadOnly(boolean readOnly) {
158
        super.setReadOnly(readOnly);
159
        save.setVisible(!readOnly);
160
        cancel.setCaption(readOnly ? "Close" : "Cancel");
161
    }
162

    
163
    /**
164
     * @return
165
     * @return
166
     */
167
    protected AbstractLayout getToolBar() {
168
        return toolBar;
169
    }
170

    
171
    /**
172
     * @return
173
     * @return
174
     */
175
    protected void toolBarAdd(Component c) {
176
        toolBar.addComponent(c, toolBar.getComponentIndex(toolBarButtonGroup) - 1);
177
        updateToolBarVisibility();
178
    }
179

    
180
    /**
181
     * @return
182
     * @return
183
     */
184
    protected void toolBarButtonGroupAdd(Component c) {
185
        toolBarButtonGroup.addComponent(c);
186
        updateToolBarVisibility();
187
    }
188

    
189
    /**
190
     * @return
191
     * @return
192
     */
193
    protected void toolBarButtonGroupRemove(Component c) {
194
        toolBarButtonGroup.removeComponent(c);
195
        updateToolBarVisibility();
196
    }
197

    
198
    /**
199
     *
200
     */
201
    private void updateToolBarVisibility() {
202
        toolBar.setVisible(toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1);
203

    
204
    }
205

    
206
    /**
207
     * The top tool-bar is initially invisible.
208
     *
209
     * @param visible
210
     */
211
    protected void setToolBarVisible(boolean visible){
212
        toolBar.setVisible(true);
213
    }
214

    
215

    
216

    
217
    // ------------------------ event handler ------------------------ //
218

    
219
    private class SaveHandler implements CommitHandler {
220

    
221
        private static final long serialVersionUID = 2047223089707080659L;
222

    
223
        @Override
224
        public void preCommit(CommitEvent commitEvent) throws CommitException {
225
            logger.debug("preCommit");
226
            // notify the presenter to start a transaction
227
            eventBus.publishEvent(new EditorPreSaveEvent<DTO>(AbstractPopupEditor.this, fieldGroup.getItemDataSource().getBean()));
228
        }
229

    
230
        @Override
231
        public void postCommit(CommitEvent commitEvent) throws CommitException {
232
            try {
233
                // notify the presenter to persist the bean and to commit the transaction
234
                eventBus.publishEvent(new EditorSaveEvent<DTO>(AbstractPopupEditor.this, fieldGroup.getItemDataSource().getBean()));
235

    
236
                // notify the NavigationManagerBean to close the window and to dispose the view
237
                eventBus.publishEvent(new DoneWithPopupEvent(AbstractPopupEditor.this, Reason.SAVE));
238
            } catch (Exception e) {
239
                throw new CommitException("Failed to store data to backend", e);
240
            }
241
        }
242
    }
243

    
244
    protected void addCommitHandler(CommitHandler commitHandler) {
245
        fieldGroup.addCommitHandler(commitHandler);
246
    }
247

    
248

    
249
    /**
250
     * Cancel editing and discard all modifications.
251
     */
252
    @Override
253
    public void cancel() {
254
        fieldGroup.discard();
255
        eventBus.publishEvent(new DoneWithPopupEvent(this, Reason.CANCEL));
256
    }
257

    
258
    /**
259
     * @return
260
     */
261
    private void delete() {
262
        eventBus.publishEvent(new EditorDeleteEvent(this, fieldGroup.getItemDataSource().getBean()));
263
        eventBus.publishEvent(new DoneWithPopupEvent(this, Reason.DELETE));
264
    }
265

    
266
    /**
267
     * Save the changes made in the editor.
268
     */
269
    private void save() {
270
        try {
271
            fieldGroup.commit();
272
        } catch (CommitException e) {
273
            fieldGroup.getFields().forEach(f -> ((AbstractField<?>)f).setValidationVisible(true));
274
            if(e.getCause() != null && e.getCause() instanceof FieldGroupInvalidValueException){
275
                FieldGroupInvalidValueException invalidValueException = (FieldGroupInvalidValueException)e.getCause();
276
                updateFieldNotifications(invalidValueException.getInvalidFields());
277
                Notification.show("The entered data in " + invalidValueException.getInvalidFields().size() + " fields is incomplete or invalid.");
278
            } else if(e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof PermissionDeniedException){
279
                PermissionDeniedException permissionDeniedException = (PermissionDeniedException)e.getCause().getCause();
280
                Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
281
            }
282
            else {
283
                Logger.getLogger(this.getClass()).error("Error saving", e);
284
                Notification.show("Error saving", Type.ERROR_MESSAGE);
285
            }
286
        }
287
    }
288

    
289
    /**
290
     * @param invalidFields
291
     */
292
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
293
        for(Field<?> f : invalidFields.keySet()){
294
            if(f instanceof AbstractField){
295
                String message = invalidFields.get(f).getHtmlMessage();
296
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
297
            }
298
        }
299

    
300
    }
301

    
302
    // ------------------------ field adding methods ------------------------ //
303

    
304

    
305
    protected TextField addTextField(String caption, String propertyId) {
306
        return addField(new TextField(caption), propertyId);
307
    }
308

    
309
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
310
            int column2, int row2)
311
            throws OverlapsException, OutOfBoundsException {
312
        return addField(new TextField(caption), propertyId, column1, row1, column2, row2);
313
    }
314

    
315
    protected TextField addTextField(String caption, String propertyId, int column, int row)
316
            throws OverlapsException, OutOfBoundsException {
317
        return addField(new TextField(caption), propertyId, column, row);
318
    }
319

    
320
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
321
            int column2, int row2)
322
            throws OverlapsException, OutOfBoundsException {
323

    
324
        SwitchableTextField field = new SwitchableTextField(caption);
325
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
326
        addComponent(field, column1, row1, column2, row2);
327
        return field;
328
    }
329

    
330
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
331
            throws OverlapsException, OutOfBoundsException {
332

    
333
        SwitchableTextField field = new SwitchableTextField(caption);
334
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
335
        addComponent(field, column, row);
336
        return field;
337
    }
338

    
339
    protected PopupDateField addDateField(String caption, String propertyId) {
340
        return addField(new PopupDateField(caption), propertyId);
341
    }
342

    
343
    protected CheckBox addCheckBox(String caption, String propertyId) {
344
        return addField(new CheckBox(caption), propertyId);
345
    }
346

    
347
    protected <T extends Field> T addField(T field, String propertyId) {
348
        fieldGroup.bind(field, propertyId);
349
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
350
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
351
        }
352
        addComponent(field);
353
        return field;
354
    }
355

    
356
    /**
357
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
358
     *
359
     * @param field
360
     *            the field to be added, not <code>null</code>.
361
     * @param propertyId
362
     * @param column
363
     *            the column index, starting from 0.
364
     * @param row
365
     *            the row index, starting from 0.
366
     * @throws OverlapsException
367
     *             if the new component overlaps with any of the components
368
     *             already in the grid.
369
     * @throws OutOfBoundsException
370
     *             if the cell is outside the grid area.
371
     */
372
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
373
            throws OverlapsException, OutOfBoundsException {
374
        fieldGroup.bind(field, propertyId);
375
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
376
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
377
        }
378
        addComponent(field, column, row);
379
        return field;
380
    }
381

    
382
    /**
383
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
384
     *
385
     * @param field
386
     * @param propertyId
387
     * @param column1
388
     * @param row1
389
     * @param column2
390
     * @param row2
391
     * @return
392
     * @throws OverlapsException
393
     * @throws OutOfBoundsException
394
     */
395
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
396
            int column2, int row2)
397
            throws OverlapsException, OutOfBoundsException {
398
        if(propertyId != null){
399
            fieldGroup.bind(field, propertyId);
400
            if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
401
                ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
402
            }
403
        }
404
        addComponent(field, column1, row1, column2, row2);
405
        return field;
406
    }
407

    
408
    protected Field<?> getField(Object propertyId){
409
        return fieldGroup.getField(propertyId);
410
    }
411

    
412
    protected void addComponent(Component component) {
413
        fieldLayout.addComponent(component);
414
        applyDefaultComponentStyles(component);
415
    }
416

    
417
    /**
418
     * @param component
419
     */
420
    public void applyDefaultComponentStyles(Component component) {
421
        component.addStyleName(getDefaultComponentStyles());
422
    }
423

    
424
    protected abstract String getDefaultComponentStyles();
425

    
426
    /**
427
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
428
     * <p>
429
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
430
     * the area.) End coordinates (SouthEast corner of the area) are the same as
431
     * column1,row1. The coordinates are zero-based. Component width and height
432
     * is 1.
433
     *
434
     * @param component
435
     *            the component to be added, not <code>null</code>.
436
     * @param column
437
     *            the column index, starting from 0.
438
     * @param row
439
     *            the row index, starting from 0.
440
     * @throws OverlapsException
441
     *             if the new component overlaps with any of the components
442
     *             already in the grid.
443
     * @throws OutOfBoundsException
444
     *             if the cell is outside the grid area.
445
     */
446
    public void addComponent(Component component, int column, int row)
447
            throws OverlapsException, OutOfBoundsException {
448
        applyDefaultComponentStyles(component);
449
        gridLayout().addComponent(component, column, row, column, row);
450
    }
451

    
452
    /**
453
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
454
     * <p>
455
     * Adds a component to the grid in the specified area. The area is defined
456
     * by specifying the upper left corner (column1, row1) and the lower right
457
     * corner (column2, row2) of the area. The coordinates are zero-based.
458
     * </p>
459
     *
460
     * <p>
461
     * If the area overlaps with any of the existing components already present
462
     * in the grid, the operation will fail and an {@link OverlapsException} is
463
     * thrown.
464
     * </p>
465
     *
466
     * @param component
467
     *            the component to be added, not <code>null</code>.
468
     * @param column1
469
     *            the column of the upper left corner of the area <code>c</code>
470
     *            is supposed to occupy. The leftmost column has index 0.
471
     * @param row1
472
     *            the row of the upper left corner of the area <code>c</code> is
473
     *            supposed to occupy. The topmost row has index 0.
474
     * @param column2
475
     *            the column of the lower right corner of the area
476
     *            <code>c</code> is supposed to occupy.
477
     * @param row2
478
     *            the row of the lower right corner of the area <code>c</code>
479
     *            is supposed to occupy.
480
     * @throws OverlapsException
481
     *             if the new component overlaps with any of the components
482
     *             already in the grid.
483
     * @throws OutOfBoundsException
484
     *             if the cells are outside the grid area.
485
     */
486
    public void addComponent(Component component, int column1, int row1,
487
            int column2, int row2)
488
            throws OverlapsException, OutOfBoundsException {
489
        applyDefaultComponentStyles(component);
490
        gridLayout().addComponent(component, column1, row1, column2, row2);
491
    }
492

    
493

    
494

    
495
    // ------------------------ data binding ------------------------ //
496

    
497
    protected void bindDesign(Component component) {
498
        fieldLayout.removeAllComponents();
499
        fieldGroup.bindMemberFields(component);
500
        fieldLayout.addComponent(component);
501
    }
502

    
503
    public void showInEditor(DTO beanToEdit) {
504

    
505
        fieldGroup.setItemDataSource(beanToEdit);
506
        afterItemDataSourceSet();
507
    }
508

    
509
    /**
510
     * Returns the bean to be edited.
511
     *
512
     * @return
513
     */
514
    public DTO getBean() {
515
        return fieldGroup.getItemDataSource().getBean();
516
    }
517

    
518
    /**
519
     * This method is called after setting the item data source whereby the {@link FieldGroup#configureField(Field<?> field)} method will be called.
520
     * In this method all fields are set to default states defined for the fieldGroup.
521
     * <p>
522
     * You can now implement this method if you need to configure the enable state of fields
523
     * individually.
524
     */
525
    protected void afterItemDataSourceSet() {
526

    
527
    }
528

    
529
    // ------------------------ issue related temporary solutions --------------------- //
530
    /**
531
     *
532
     * @return
533
     * @deprecated see #6673
534
     */
535
    @Deprecated
536
    public P presenter() {
537
        return getPresenter();
538
    }
539
}
(4-4/8)