Project

General

Profile

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

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

    
56
    private BeanFieldGroup<DTO> fieldGroup;
57

    
58
    private VerticalLayout mainLayout;
59

    
60
    private Layout fieldLayout;
61

    
62
    private HorizontalLayout buttonLayout;
63

    
64
    private Button save;
65

    
66
    private Button cancel;
67

    
68
    private GridLayout _gridLayoutCache;
69

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

    
72
        setWidthUndefined();
73

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

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

    
80
        setCompositionRoot(mainLayout);
81

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

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

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

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

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

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

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

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

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

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

    
137
    // ------------------------ event handler ------------------------ //
138

    
139
    private class SaveHandler implements CommitHandler {
140

    
141
        private static final long serialVersionUID = 2047223089707080659L;
142

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

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

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

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

    
168

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

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

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

    
201
    }
202

    
203
    // ------------------------ field adding methods ------------------------ //
204

    
205

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

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

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

    
221
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
222
            int column2, int row2)
223
            throws OverlapsException, OutOfBoundsException {
224

    
225
        SwitchableTextField field = new SwitchableTextField(caption);
226
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
227
        addComponent(field, column1, row1, column2, row2);
228
        return field;
229
    }
230

    
231
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
232
            throws OverlapsException, OutOfBoundsException {
233

    
234
        SwitchableTextField field = new SwitchableTextField(caption);
235
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
236
        addComponent(field, column, row);
237
        return field;
238
    }
239

    
240
    protected PopupDateField addDateField(String caption, String propertyId) {
241
        return addField(new PopupDateField(caption), propertyId);
242
    }
243

    
244
    protected CheckBox addCheckBox(String caption, String propertyId) {
245
        return addField(new CheckBox(caption), propertyId);
246
    }
247

    
248
    protected <T extends Field> T addField(T field, String propertyId) {
249
        fieldGroup.bind(field, propertyId);
250
        addComponent(field);
251
        return field;
252
    }
253

    
254
    /**
255
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
256
     *
257
     * @param field
258
     *            the field to be added, not <code>null</code>.
259
     * @param propertyId
260
     * @param column
261
     *            the column index, starting from 0.
262
     * @param row
263
     *            the row index, starting from 0.
264
     * @throws OverlapsException
265
     *             if the new component overlaps with any of the components
266
     *             already in the grid.
267
     * @throws OutOfBoundsException
268
     *             if the cell is outside the grid area.
269
     */
270
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
271
            throws OverlapsException, OutOfBoundsException {
272
        fieldGroup.bind(field, propertyId);
273
        addComponent(field, column, row);
274
        return field;
275
    }
276

    
277
    /**
278
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
279
     *
280
     * @param field
281
     * @param propertyId
282
     * @param column1
283
     * @param row1
284
     * @param column2
285
     * @param row2
286
     * @return
287
     * @throws OverlapsException
288
     * @throws OutOfBoundsException
289
     */
290
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
291
            int column2, int row2)
292
            throws OverlapsException, OutOfBoundsException {
293
        fieldGroup.bind(field, propertyId);
294
        addComponent(field, column1, row1, column2, row2);
295
        return field;
296
    }
297

    
298
    protected void addComponent(Component component) {
299
        fieldLayout.addComponent(component);
300
        applyDefaultComponentStyles(component);
301
    }
302

    
303
    /**
304
     * @param component
305
     */
306
    public void applyDefaultComponentStyles(Component component) {
307
        component.addStyleName(getDefaultComponentStyles());
308
    }
309

    
310
    protected abstract String getDefaultComponentStyles();
311

    
312
    /**
313
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
314
     * <p>
315
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
316
     * the area.) End coordinates (SouthEast corner of the area) are the same as
317
     * column1,row1. The coordinates are zero-based. Component width and height
318
     * is 1.
319
     *
320
     * @param component
321
     *            the component to be added, not <code>null</code>.
322
     * @param column
323
     *            the column index, starting from 0.
324
     * @param row
325
     *            the row index, starting from 0.
326
     * @throws OverlapsException
327
     *             if the new component overlaps with any of the components
328
     *             already in the grid.
329
     * @throws OutOfBoundsException
330
     *             if the cell is outside the grid area.
331
     */
332
    public void addComponent(Component component, int column, int row)
333
            throws OverlapsException, OutOfBoundsException {
334
        applyDefaultComponentStyles(component);
335
        gridLayout().addComponent(component, column, row, column, row);
336
    }
337

    
338
    /**
339
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
340
     * <p>
341
     * Adds a component to the grid in the specified area. The area is defined
342
     * by specifying the upper left corner (column1, row1) and the lower right
343
     * corner (column2, row2) of the area. The coordinates are zero-based.
344
     * </p>
345
     *
346
     * <p>
347
     * If the area overlaps with any of the existing components already present
348
     * in the grid, the operation will fail and an {@link OverlapsException} is
349
     * thrown.
350
     * </p>
351
     *
352
     * @param component
353
     *            the component to be added, not <code>null</code>.
354
     * @param column1
355
     *            the column of the upper left corner of the area <code>c</code>
356
     *            is supposed to occupy. The leftmost column has index 0.
357
     * @param row1
358
     *            the row of the upper left corner of the area <code>c</code> is
359
     *            supposed to occupy. The topmost row has index 0.
360
     * @param column2
361
     *            the column of the lower right corner of the area
362
     *            <code>c</code> is supposed to occupy.
363
     * @param row2
364
     *            the row of the lower right corner of the area <code>c</code>
365
     *            is supposed to occupy.
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 cells are outside the grid area.
371
     */
372
    public void addComponent(Component component, int column1, int row1,
373
            int column2, int row2)
374
            throws OverlapsException, OutOfBoundsException {
375
        applyDefaultComponentStyles(component);
376
        gridLayout().addComponent(component, column1, row1, column2, row2);
377
    }
378

    
379

    
380

    
381
    // ------------------------ data binding ------------------------ //
382

    
383
    protected void bindDesign(Component component) {
384
        fieldLayout.removeAllComponents();
385
        fieldGroup.bindMemberFields(component);
386
        fieldLayout.addComponent(component);
387
    }
388

    
389
    public void showInEditor(DTO beanToEdit) {
390
        fieldGroup.setItemDataSource(beanToEdit);
391
    }
392
}
(4-4/10)