Project

General

Profile

Download (13 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.vaadin.ui.view.DoneWithPopupEvent;
45
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
46

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

    
55
    private BeanFieldGroup<DTO> fieldGroup;
56

    
57
    private VerticalLayout mainLayout;
58

    
59
    private Layout fieldLayout;
60

    
61
    private HorizontalLayout buttonLayout;
62

    
63
    private Button save;
64

    
65
    private Button cancel;
66

    
67
    private GridLayout _gridLayoutCache;
68

    
69
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
70

    
71
        setWidthUndefined();
72

    
73
        mainLayout = new VerticalLayout();
74
        mainLayout.setWidthUndefined();
75

    
76
        fieldGroup = new BeanFieldGroup<>(dtoType);
77
        fieldGroup.addCommitHandler(new SaveHandler());
78

    
79
        setCompositionRoot(mainLayout);
80

    
81
        fieldLayout = layout;
82
        fieldLayout.setWidthUndefined();
83
        if(fieldLayout instanceof AbstractOrderedLayout){
84
            ((AbstractOrderedLayout)fieldLayout).setSpacing(true);
85
        }
86

    
87
        buttonLayout = new HorizontalLayout();
88
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
89
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
90
        buttonLayout.setSpacing(true);
91

    
92
        save = new Button("Save", FontAwesome.SAVE);
93
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
94
        save.addClickListener(e -> onSaveClicked());
95

    
96
        cancel = new Button("Cancel", FontAwesome.TRASH);
97
        cancel.addClickListener(e -> onCancelClicked());
98

    
99
        buttonLayout.addComponents(save, cancel);
100
        buttonLayout.setExpandRatio(save, 1);
101
        buttonLayout.setComponentAlignment(save, Alignment.TOP_RIGHT);
102
        buttonLayout.setComponentAlignment(cancel, Alignment.TOP_RIGHT);
103

    
104
        mainLayout.addComponents(fieldLayout, buttonLayout);
105
    }
106

    
107
    protected VerticalLayout getMainLayout() {
108
        return mainLayout;
109
    }
110

    
111
    protected Layout getFieldLayout() {
112
        return fieldLayout;
113
    }
114

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

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

    
136
    // ------------------------ event handler ------------------------ //
137

    
138
    private class SaveHandler implements CommitHandler {
139

    
140
        private static final long serialVersionUID = 2047223089707080659L;
141

    
142
        @Override
143
        public void preCommit(CommitEvent commitEvent) throws CommitException {
144
            logger.debug("preCommit");
145
            // notify the presenter to start a transaction
146
            eventBus.publishEvent(new EditorPreSaveEvent(commitEvent));
147
        }
148

    
149
        @Override
150
        public void postCommit(CommitEvent commitEvent) throws CommitException {
151
            try {
152
                // notify the presenter to persist the bean and to commit the transaction
153
                eventBus.publishEvent(new EditorSaveEvent(commitEvent));
154

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

    
163
    protected void addCommitHandler(CommitHandler commitHandler) {
164
        fieldGroup.addCommitHandler(commitHandler);
165
    }
166

    
167

    
168
    private void onCancelClicked() {
169
        fieldGroup.discard();
170
        eventBus.publishEvent(new DoneWithPopupEvent(this, Reason.CANCEL));
171
    }
172

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

    
189
    /**
190
     * @param invalidFields
191
     */
192
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
193
        for(Field<?> f : invalidFields.keySet()){
194
            if(f instanceof AbstractField){
195
                String message = invalidFields.get(f).getHtmlMessage();
196
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
197
            }
198
        }
199

    
200
    }
201

    
202
    // ------------------------ field adding methods ------------------------ //
203

    
204

    
205
    protected TextField addTextField(String caption, String propertyId) {
206
        return addField(new TextField(caption), propertyId);
207
    }
208

    
209
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
210
            int column2, int row2)
211
            throws OverlapsException, OutOfBoundsException {
212
        return addField(new TextField(caption), propertyId, column1, row1, column2, row2);
213
    }
214

    
215
    protected TextField addTextField(String caption, String propertyId, int column, int row)
216
            throws OverlapsException, OutOfBoundsException {
217
        return addField(new TextField(caption), propertyId, column, row);
218
    }
219

    
220
    protected PopupDateField addDateField(String caption, String propertyId) {
221
        return addField(new PopupDateField(caption), propertyId);
222
    }
223

    
224
    protected CheckBox addCheckBox(String caption, String propertyId) {
225
        return addField(new CheckBox(caption), propertyId);
226
    }
227

    
228
    protected <T extends Field> T addField(T field, String propertyId) {
229
        fieldGroup.bind(field, propertyId);
230
        addComponent(field);
231
        return field;
232
    }
233

    
234
    /**
235
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
236
     *
237
     * @param field
238
     *            the field to be added, not <code>null</code>.
239
     * @param propertyId
240
     * @param column
241
     *            the column index, starting from 0.
242
     * @param row
243
     *            the row index, starting from 0.
244
     * @throws OverlapsException
245
     *             if the new component overlaps with any of the components
246
     *             already in the grid.
247
     * @throws OutOfBoundsException
248
     *             if the cell is outside the grid area.
249
     */
250
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
251
            throws OverlapsException, OutOfBoundsException {
252
        fieldGroup.bind(field, propertyId);
253
        addComponent(field, column, row);
254
        return field;
255
    }
256

    
257
    /**
258
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
259
     *
260
     * @param field
261
     * @param propertyId
262
     * @param column1
263
     * @param row1
264
     * @param column2
265
     * @param row2
266
     * @return
267
     * @throws OverlapsException
268
     * @throws OutOfBoundsException
269
     */
270
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
271
            int column2, int row2)
272
            throws OverlapsException, OutOfBoundsException {
273
        fieldGroup.bind(field, propertyId);
274
        addComponent(field, column1, row1, column2, row2);
275
        return field;
276
    }
277

    
278
    protected void addComponent(Component component) {
279
        fieldLayout.addComponent(component);
280
        applyDefaultComponentStyles(component);
281
    }
282

    
283
    /**
284
     * @param component
285
     */
286
    public void applyDefaultComponentStyles(Component component) {
287
        component.addStyleName(getDefaultComponentStyles());
288
    }
289

    
290
    protected abstract String getDefaultComponentStyles();
291

    
292
    /**
293
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
294
     * <p>
295
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
296
     * the area.) End coordinates (SouthEast corner of the area) are the same as
297
     * column1,row1. The coordinates are zero-based. Component width and height
298
     * is 1.
299
     *
300
     * @param component
301
     *            the component to be added, not <code>null</code>.
302
     * @param column
303
     *            the column index, starting from 0.
304
     * @param row
305
     *            the row index, starting from 0.
306
     * @throws OverlapsException
307
     *             if the new component overlaps with any of the components
308
     *             already in the grid.
309
     * @throws OutOfBoundsException
310
     *             if the cell is outside the grid area.
311
     */
312
    public void addComponent(Component component, int column, int row)
313
            throws OverlapsException, OutOfBoundsException {
314
        applyDefaultComponentStyles(component);
315
        gridLayout().addComponent(component, column, row, column, row);
316
    }
317

    
318
    /**
319
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
320
     * <p>
321
     * Adds a component to the grid in the specified area. The area is defined
322
     * by specifying the upper left corner (column1, row1) and the lower right
323
     * corner (column2, row2) of the area. The coordinates are zero-based.
324
     * </p>
325
     *
326
     * <p>
327
     * If the area overlaps with any of the existing components already present
328
     * in the grid, the operation will fail and an {@link OverlapsException} is
329
     * thrown.
330
     * </p>
331
     *
332
     * @param component
333
     *            the component to be added, not <code>null</code>.
334
     * @param column1
335
     *            the column of the upper left corner of the area <code>c</code>
336
     *            is supposed to occupy. The leftmost column has index 0.
337
     * @param row1
338
     *            the row of the upper left corner of the area <code>c</code> is
339
     *            supposed to occupy. The topmost row has index 0.
340
     * @param column2
341
     *            the column of the lower right corner of the area
342
     *            <code>c</code> is supposed to occupy.
343
     * @param row2
344
     *            the row of the lower right corner of the area <code>c</code>
345
     *            is supposed to occupy.
346
     * @throws OverlapsException
347
     *             if the new component overlaps with any of the components
348
     *             already in the grid.
349
     * @throws OutOfBoundsException
350
     *             if the cells are outside the grid area.
351
     */
352
    public void addComponent(Component component, int column1, int row1,
353
            int column2, int row2)
354
            throws OverlapsException, OutOfBoundsException {
355
        applyDefaultComponentStyles(component);
356
        gridLayout().addComponent(component, column1, row1, column2, row2);
357
    }
358

    
359

    
360

    
361
    // ------------------------ data binding ------------------------ //
362

    
363
    protected void bindDesign(Component component) {
364
        fieldLayout.removeAllComponents();
365
        fieldGroup.bindMemberFields(component);
366
        fieldLayout.addComponent(component);
367
    }
368

    
369
    public void showInEditor(DTO beanToEdit) {
370
        fieldGroup.setItemDataSource(beanToEdit);
371
    }
372
}
(4-4/10)