Project

General

Profile

Download (12.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.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
        }
145

    
146
        @Override
147
        public void postCommit(CommitEvent commitEvent) throws CommitException {
148
            try {
149
                // notify the presenter to persist the bean
150
                eventBus.publishEvent(new EditorSaveEvent(commitEvent));
151

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

    
160
    protected void addCommitHandler(CommitHandler commitHandler) {
161
        fieldGroup.addCommitHandler(commitHandler);
162
    }
163

    
164

    
165
    private void onCancelClicked() {
166
        fieldGroup.discard();
167
        eventBus.publishEvent(new DoneWithPopupEvent(this, Reason.CANCEL));
168
    }
169

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

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

    
197
    }
198

    
199
    // ------------------------ field adding methods ------------------------ //
200

    
201

    
202
    protected TextField addTextField(String caption, String propertyId) {
203
        return addField(new TextField(caption), propertyId);
204
    }
205

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

    
212
    protected TextField addTextField(String caption, String propertyId, int column, int row)
213
            throws OverlapsException, OutOfBoundsException {
214
        return addField(new TextField(caption), propertyId, column, row);
215
    }
216

    
217
    protected PopupDateField addDateField(String caption, String propertyId) {
218
        return addField(new PopupDateField(caption), propertyId);
219
    }
220

    
221
    protected CheckBox addCheckBox(String caption, String propertyId) {
222
        return addField(new CheckBox(caption), propertyId);
223
    }
224

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

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

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

    
275
    protected void addComponent(Component component) {
276
        fieldLayout.addComponent(component);
277
        applyDefaultComponentStyles(component);
278
    }
279

    
280
    /**
281
     * @param component
282
     */
283
    public void applyDefaultComponentStyles(Component component) {
284
        component.addStyleName(getDefaultComponentStyles());
285
    }
286

    
287
    protected abstract String getDefaultComponentStyles();
288

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

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

    
356

    
357

    
358
    // ------------------------ data binding ------------------------ //
359

    
360
    protected void bindDesign(Component component) {
361
        fieldLayout.removeAllComponents();
362
        fieldGroup.bindMemberFields(component);
363
        fieldLayout.addComponent(component);
364
    }
365

    
366
    public void showInEditor(DTO beanToEdit) {
367
        fieldGroup.setItemDataSource(beanToEdit);
368
    }
369
}
(2-2/7)