Project

General

Profile

Download (15.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.AbstractOrderedLayout;
27
import com.vaadin.ui.Alignment;
28
import com.vaadin.ui.Button;
29
import com.vaadin.ui.CheckBox;
30
import com.vaadin.ui.Component;
31
import com.vaadin.ui.Field;
32
import com.vaadin.ui.GridLayout;
33
import com.vaadin.ui.GridLayout.OutOfBoundsException;
34
import com.vaadin.ui.GridLayout.OverlapsException;
35
import com.vaadin.ui.HorizontalLayout;
36
import com.vaadin.ui.Layout;
37
import com.vaadin.ui.Notification;
38
import com.vaadin.ui.Notification.Type;
39
import com.vaadin.ui.PopupDateField;
40
import com.vaadin.ui.TextField;
41
import com.vaadin.ui.VerticalLayout;
42
import com.vaadin.ui.themes.ValoTheme;
43

    
44
import eu.etaxonomy.cdm.database.PermissionDeniedException;
45
import eu.etaxonomy.vaadin.component.NestedFieldGroup;
46
import eu.etaxonomy.vaadin.component.SwitchableTextField;
47
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
48
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
49

    
50
/**
51
 * @author a.kohlbecker
52
 * @since Apr 5, 2017
53
 *
54
 */
55
public abstract class AbstractPopupEditor<DTO extends Object, P extends AbstractEditorPresenter<DTO>>
56
    extends AbstractPopupView<P> {
57

    
58
    private BeanFieldGroup<DTO> fieldGroup;
59

    
60
    private VerticalLayout mainLayout;
61

    
62
    private Layout fieldLayout;
63

    
64
    private HorizontalLayout buttonLayout;
65

    
66
    private Button save;
67

    
68
    private Button cancel;
69

    
70
    private GridLayout _gridLayoutCache;
71

    
72
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
73

    
74
        setWidthUndefined();
75

    
76
        mainLayout = new VerticalLayout();
77
        mainLayout.setWidthUndefined();
78

    
79
        fieldGroup = new BeanFieldGroup<>(dtoType);
80
        fieldGroup.addCommitHandler(new SaveHandler());
81

    
82
        setCompositionRoot(mainLayout);
83

    
84
        fieldLayout = layout;
85
        fieldLayout.setWidthUndefined();
86
        if(fieldLayout instanceof AbstractOrderedLayout){
87
            ((AbstractOrderedLayout)fieldLayout).setSpacing(true);
88
        }
89

    
90
        buttonLayout = new HorizontalLayout();
91
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
92
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
93
        buttonLayout.setSpacing(true);
94

    
95
        save = new Button("Save", FontAwesome.SAVE);
96
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
97
        save.addClickListener(e -> onSaveClicked());
98

    
99
        cancel = new Button("Cancel", FontAwesome.TRASH);
100
        cancel.addClickListener(e -> onCancelClicked());
101

    
102
        buttonLayout.addComponents(save, cancel);
103
        buttonLayout.setExpandRatio(save, 1);
104
        buttonLayout.setComponentAlignment(save, Alignment.TOP_RIGHT);
105
        buttonLayout.setComponentAlignment(cancel, Alignment.TOP_RIGHT);
106

    
107
        mainLayout.addComponents(fieldLayout, buttonLayout);
108
    }
109

    
110
    protected VerticalLayout getMainLayout() {
111
        return mainLayout;
112
    }
113

    
114
    protected Layout getFieldLayout() {
115
        return fieldLayout;
116
    }
117

    
118
    /**
119
     * @return
120
     */
121
    private GridLayout gridLayout() {
122
        if(_gridLayoutCache == null){
123
            if(fieldLayout instanceof GridLayout){
124
                _gridLayoutCache = (GridLayout)fieldLayout;
125
            } else {
126
                throw new RuntimeException("The fieldlayout of this editor is not a GridLayout");
127
            }
128
        }
129
        return _gridLayoutCache;
130
    }
131

    
132
    @Override
133
    public void setReadOnly(boolean readOnly) {
134
        super.setReadOnly(readOnly);
135
        save.setVisible(!readOnly);
136
        cancel.setCaption(readOnly ? "Close" : "Cancel");
137
    }
138

    
139
    // ------------------------ event handler ------------------------ //
140

    
141
    private class SaveHandler implements CommitHandler {
142

    
143
        private static final long serialVersionUID = 2047223089707080659L;
144

    
145
        @Override
146
        public void preCommit(CommitEvent commitEvent) throws CommitException {
147
            logger.debug("preCommit");
148
            // notify the presenter to start a transaction
149
            eventBus.publishEvent(new EditorPreSaveEvent(commitEvent, AbstractPopupEditor.this));
150
        }
151

    
152
        @Override
153
        public void postCommit(CommitEvent commitEvent) throws CommitException {
154
            try {
155
                // notify the presenter to persist the bean and to commit the transaction
156
                eventBus.publishEvent(new EditorSaveEvent(commitEvent, AbstractPopupEditor.this));
157

    
158
                // notify the NavigationManagerBean to close the window and to dispose the view
159
                eventBus.publishEvent(new DoneWithPopupEvent(AbstractPopupEditor.this, Reason.SAVE));
160
            } catch (Exception e) {
161
                throw new CommitException("Failed to store data to backend", e);
162
            }
163
        }
164
    }
165

    
166
    protected void addCommitHandler(CommitHandler commitHandler) {
167
        fieldGroup.addCommitHandler(commitHandler);
168
    }
169

    
170

    
171
    private void onCancelClicked() {
172
        fieldGroup.discard();
173
        eventBus.publishEvent(new DoneWithPopupEvent(this, Reason.CANCEL));
174
    }
175

    
176
    private void onSaveClicked() {
177
        try {
178
            fieldGroup.commit();
179
        } catch (CommitException e) {
180
            fieldGroup.getFields().forEach(f -> ((AbstractField<?>)f).setValidationVisible(true));
181
            if(e.getCause() != null && e.getCause() instanceof FieldGroupInvalidValueException){
182
                FieldGroupInvalidValueException invalidValueException = (FieldGroupInvalidValueException)e.getCause();
183
                updateFieldNotifications(invalidValueException.getInvalidFields());
184
                Notification.show("The entered data in " + invalidValueException.getInvalidFields().size() + " fields is incomplete or invalid.");
185
            } else if(e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof PermissionDeniedException){
186
                PermissionDeniedException permissionDeniedException = (PermissionDeniedException)e.getCause().getCause();
187
                Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
188
            }
189
            else {
190
                Logger.getLogger(this.getClass()).error("Error saving", e);
191
                Notification.show("Error saving", Type.ERROR_MESSAGE);
192
            }
193
        }
194
    }
195

    
196
    /**
197
     * @param invalidFields
198
     */
199
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
200
        for(Field<?> f : invalidFields.keySet()){
201
            if(f instanceof AbstractField){
202
                String message = invalidFields.get(f).getHtmlMessage();
203
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
204
            }
205
        }
206

    
207
    }
208

    
209
    // ------------------------ field adding methods ------------------------ //
210

    
211

    
212
    protected TextField addTextField(String caption, String propertyId) {
213
        return addField(new TextField(caption), propertyId);
214
    }
215

    
216
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
217
            int column2, int row2)
218
            throws OverlapsException, OutOfBoundsException {
219
        return addField(new TextField(caption), propertyId, column1, row1, column2, row2);
220
    }
221

    
222
    protected TextField addTextField(String caption, String propertyId, int column, int row)
223
            throws OverlapsException, OutOfBoundsException {
224
        return addField(new TextField(caption), propertyId, column, row);
225
    }
226

    
227
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
228
            int column2, int row2)
229
            throws OverlapsException, OutOfBoundsException {
230

    
231
        SwitchableTextField field = new SwitchableTextField(caption);
232
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
233
        addComponent(field, column1, row1, column2, row2);
234
        return field;
235
    }
236

    
237
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
238
            throws OverlapsException, OutOfBoundsException {
239

    
240
        SwitchableTextField field = new SwitchableTextField(caption);
241
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
242
        addComponent(field, column, row);
243
        return field;
244
    }
245

    
246
    protected PopupDateField addDateField(String caption, String propertyId) {
247
        return addField(new PopupDateField(caption), propertyId);
248
    }
249

    
250
    protected CheckBox addCheckBox(String caption, String propertyId) {
251
        return addField(new CheckBox(caption), propertyId);
252
    }
253

    
254
    protected <T extends Field> T addField(T field, String propertyId) {
255
        fieldGroup.bind(field, propertyId);
256
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
257
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
258
        }
259
        addComponent(field);
260
        return field;
261
    }
262

    
263
    /**
264
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
265
     *
266
     * @param field
267
     *            the field to be added, not <code>null</code>.
268
     * @param propertyId
269
     * @param column
270
     *            the column index, starting from 0.
271
     * @param row
272
     *            the row index, starting from 0.
273
     * @throws OverlapsException
274
     *             if the new component overlaps with any of the components
275
     *             already in the grid.
276
     * @throws OutOfBoundsException
277
     *             if the cell is outside the grid area.
278
     */
279
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
280
            throws OverlapsException, OutOfBoundsException {
281
        fieldGroup.bind(field, propertyId);
282
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
283
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
284
        }
285
        addComponent(field, column, row);
286
        return field;
287
    }
288

    
289
    /**
290
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
291
     *
292
     * @param field
293
     * @param propertyId
294
     * @param column1
295
     * @param row1
296
     * @param column2
297
     * @param row2
298
     * @return
299
     * @throws OverlapsException
300
     * @throws OutOfBoundsException
301
     */
302
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
303
            int column2, int row2)
304
            throws OverlapsException, OutOfBoundsException {
305
        if(propertyId != null){
306
            fieldGroup.bind(field, propertyId);
307
            if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
308
                ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
309
            }
310
        }
311
        addComponent(field, column1, row1, column2, row2);
312
        return field;
313
    }
314

    
315
    protected void addComponent(Component component) {
316
        fieldLayout.addComponent(component);
317
        applyDefaultComponentStyles(component);
318
    }
319

    
320
    /**
321
     * @param component
322
     */
323
    public void applyDefaultComponentStyles(Component component) {
324
        component.addStyleName(getDefaultComponentStyles());
325
    }
326

    
327
    protected abstract String getDefaultComponentStyles();
328

    
329
    /**
330
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
331
     * <p>
332
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
333
     * the area.) End coordinates (SouthEast corner of the area) are the same as
334
     * column1,row1. The coordinates are zero-based. Component width and height
335
     * is 1.
336
     *
337
     * @param component
338
     *            the component to be added, not <code>null</code>.
339
     * @param column
340
     *            the column index, starting from 0.
341
     * @param row
342
     *            the row index, starting from 0.
343
     * @throws OverlapsException
344
     *             if the new component overlaps with any of the components
345
     *             already in the grid.
346
     * @throws OutOfBoundsException
347
     *             if the cell is outside the grid area.
348
     */
349
    public void addComponent(Component component, int column, int row)
350
            throws OverlapsException, OutOfBoundsException {
351
        applyDefaultComponentStyles(component);
352
        gridLayout().addComponent(component, column, row, column, row);
353
    }
354

    
355
    /**
356
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
357
     * <p>
358
     * Adds a component to the grid in the specified area. The area is defined
359
     * by specifying the upper left corner (column1, row1) and the lower right
360
     * corner (column2, row2) of the area. The coordinates are zero-based.
361
     * </p>
362
     *
363
     * <p>
364
     * If the area overlaps with any of the existing components already present
365
     * in the grid, the operation will fail and an {@link OverlapsException} is
366
     * thrown.
367
     * </p>
368
     *
369
     * @param component
370
     *            the component to be added, not <code>null</code>.
371
     * @param column1
372
     *            the column of the upper left corner of the area <code>c</code>
373
     *            is supposed to occupy. The leftmost column has index 0.
374
     * @param row1
375
     *            the row of the upper left corner of the area <code>c</code> is
376
     *            supposed to occupy. The topmost row has index 0.
377
     * @param column2
378
     *            the column of the lower right corner of the area
379
     *            <code>c</code> is supposed to occupy.
380
     * @param row2
381
     *            the row of the lower right corner of the area <code>c</code>
382
     *            is supposed to occupy.
383
     * @throws OverlapsException
384
     *             if the new component overlaps with any of the components
385
     *             already in the grid.
386
     * @throws OutOfBoundsException
387
     *             if the cells are outside the grid area.
388
     */
389
    public void addComponent(Component component, int column1, int row1,
390
            int column2, int row2)
391
            throws OverlapsException, OutOfBoundsException {
392
        applyDefaultComponentStyles(component);
393
        gridLayout().addComponent(component, column1, row1, column2, row2);
394
    }
395

    
396

    
397

    
398
    // ------------------------ data binding ------------------------ //
399

    
400
    protected void bindDesign(Component component) {
401
        fieldLayout.removeAllComponents();
402
        fieldGroup.bindMemberFields(component);
403
        fieldLayout.addComponent(component);
404
    }
405

    
406
    public void showInEditor(DTO beanToEdit) {
407
        fieldGroup.setItemDataSource(beanToEdit);
408
        afterItemDataSourceSet();
409
    }
410

    
411
    /**
412
     * This method is called after setting the item data source whereby the {@link FieldGroup#configureField(Field<?> field)} method will be called.
413
     * In this method all fields are set to default states defined for the fieldGroup.
414
     * <p>
415
     * You can now implement this method if you need to configure the enable state of fields
416
     * individually.
417
     */
418
    protected void afterItemDataSourceSet() {
419

    
420
    }
421
}
(4-4/10)