Project

General

Profile

Download (28.9 KB) Statistics
| Branch: | Tag: | Revision:
1 0e7a9b4f Andreas Kohlbecker
/**
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 fcf1cf06 Andreas Kohlbecker
import java.util.ArrayList;
12
import java.util.Arrays;
13 07a39ef5 Andreas Kohlbecker
import java.util.HashSet;
14 fcf1cf06 Andreas Kohlbecker
import java.util.List;
15 e7bf18d2 Andreas Kohlbecker
import java.util.Map;
16 07a39ef5 Andreas Kohlbecker
import java.util.Set;
17 e8674a36 Andreas Kohlbecker
import java.util.Stack;
18 e7bf18d2 Andreas Kohlbecker
19 af10794f Andreas Kohlbecker
import org.apache.log4j.Level;
20 e7bf18d2 Andreas Kohlbecker
import org.apache.log4j.Logger;
21 be4a9789 Andreas Kohlbecker
import org.vaadin.spring.events.EventScope;
22 e7bf18d2 Andreas Kohlbecker
23
import com.vaadin.data.Validator.InvalidValueException;
24 0e7a9b4f Andreas Kohlbecker
import com.vaadin.data.fieldgroup.BeanFieldGroup;
25
import com.vaadin.data.fieldgroup.FieldGroup.CommitEvent;
26
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
27
import com.vaadin.data.fieldgroup.FieldGroup.CommitHandler;
28 e7bf18d2 Andreas Kohlbecker
import com.vaadin.data.fieldgroup.FieldGroup.FieldGroupInvalidValueException;
29
import com.vaadin.server.AbstractErrorMessage.ContentMode;
30
import com.vaadin.server.ErrorMessage.ErrorLevel;
31 0e7a9b4f Andreas Kohlbecker
import com.vaadin.server.FontAwesome;
32 e7bf18d2 Andreas Kohlbecker
import com.vaadin.server.UserError;
33 f1573a7b Andreas Kohlbecker
import com.vaadin.shared.ui.MarginInfo;
34 4a579136 Andreas Kohlbecker
import com.vaadin.ui.AbstractComponentContainer;
35 e7bf18d2 Andreas Kohlbecker
import com.vaadin.ui.AbstractField;
36 fcba7787 Andreas Kohlbecker
import com.vaadin.ui.AbstractLayout;
37 0e7a9b4f Andreas Kohlbecker
import com.vaadin.ui.AbstractOrderedLayout;
38
import com.vaadin.ui.Alignment;
39
import com.vaadin.ui.Button;
40
import com.vaadin.ui.CheckBox;
41
import com.vaadin.ui.Component;
42 fcba7787 Andreas Kohlbecker
import com.vaadin.ui.CssLayout;
43 0e7a9b4f Andreas Kohlbecker
import com.vaadin.ui.Field;
44 65508125 Andreas Kohlbecker
import com.vaadin.ui.GridLayout;
45
import com.vaadin.ui.GridLayout.OutOfBoundsException;
46
import com.vaadin.ui.GridLayout.OverlapsException;
47 af10794f Andreas Kohlbecker
import com.vaadin.ui.HasComponents;
48 0e7a9b4f Andreas Kohlbecker
import com.vaadin.ui.HorizontalLayout;
49 07a39ef5 Andreas Kohlbecker
import com.vaadin.ui.Label;
50 0e7a9b4f Andreas Kohlbecker
import com.vaadin.ui.Layout;
51 fcf1cf06 Andreas Kohlbecker
import com.vaadin.ui.Layout.MarginHandler;
52 0e7a9b4f Andreas Kohlbecker
import com.vaadin.ui.Notification;
53
import com.vaadin.ui.Notification.Type;
54
import com.vaadin.ui.PopupDateField;
55
import com.vaadin.ui.TextField;
56
import com.vaadin.ui.VerticalLayout;
57
import com.vaadin.ui.themes.ValoTheme;
58
59 6702c7e1 Andreas Kohlbecker
import eu.etaxonomy.cdm.database.PermissionDeniedException;
60 ecb93813 Andreas Kohlbecker
import eu.etaxonomy.cdm.vaadin.component.TextFieldNFix;
61 e8674a36 Andreas Kohlbecker
import eu.etaxonomy.cdm.vaadin.event.AbstractEditorAction;
62
import eu.etaxonomy.cdm.vaadin.event.AbstractEditorAction.EditorActionContext;
63 843f2ff8 Andreas Kohlbecker
import eu.etaxonomy.vaadin.component.NestedFieldGroup;
64 cd29f770 Andreas Kohlbecker
import eu.etaxonomy.vaadin.component.SwitchableTextField;
65 538800b4 Andreas Kohlbecker
import eu.etaxonomy.vaadin.mvp.event.EditorDeleteEvent;
66 c46954e3 Andreas Kohlbecker
import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
67
import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
68 0e7a9b4f Andreas Kohlbecker
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
69
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
70 af10794f Andreas Kohlbecker
import eu.etaxonomy.vaadin.util.PropertyIdPath;
71 0e7a9b4f Andreas Kohlbecker
72
/**
73 1aa951e9 Andreas Kohlbecker
 *
74
 * Optional with a delete button which can be enabled with {@link #withDeleteButton(boolean)}
75
 *
76 0e7a9b4f Andreas Kohlbecker
 * @author a.kohlbecker
77
 * @since Apr 5, 2017
78
 *
79
 */
80 c46954e3 Andreas Kohlbecker
public abstract class AbstractPopupEditor<DTO extends Object, P extends AbstractEditorPresenter<DTO, ? extends ApplicationView>>
81 0e7a9b4f Andreas Kohlbecker
    extends AbstractPopupView<P> {
82
83 a2cc9f8d Andreas Kohlbecker
    public static final Logger logger = Logger.getLogger(AbstractPopupEditor.class);
84
85 0e7a9b4f Andreas Kohlbecker
    private BeanFieldGroup<DTO> fieldGroup;
86
87
    private VerticalLayout mainLayout;
88
89
    private Layout fieldLayout;
90
91
    private HorizontalLayout buttonLayout;
92
93
    private Button save;
94
95
    private Button cancel;
96
97 538800b4 Andreas Kohlbecker
    private Button delete;
98
99 fcba7787 Andreas Kohlbecker
    private CssLayout toolBar = new CssLayout();
100
101
    private CssLayout toolBarButtonGroup = new CssLayout();
102
103 07a39ef5 Andreas Kohlbecker
    private Label statusMessageLabel = new Label();
104
105
    Set<String> statusMessages = new HashSet<>();
106
107 65508125 Andreas Kohlbecker
    private GridLayout _gridLayoutCache;
108
109 02ec8d6b Andreas Kohlbecker
    private boolean isBeanLoaded;
110
111 e8674a36 Andreas Kohlbecker
    private Stack<EditorActionContext> context = new Stack<EditorActionContext>();
112
113
    private boolean isContextUpdated;
114
115 fcf1cf06 Andreas Kohlbecker
    private boolean isAdvancedMode = false;
116
117
    private List<Component> advancedModeComponents = new ArrayList<>();
118
119
    private Button advancedModeButton;
120
121 0e7a9b4f Andreas Kohlbecker
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
122
123
        mainLayout = new VerticalLayout();
124 7954536e Andreas Kohlbecker
        // IMPORTANT: mainLayout must be set to full size otherwise the
125
        // popup window may have problems with automatic resizing of its
126
        // content.
127
        mainLayout.setSizeFull();
128 fcf1cf06 Andreas Kohlbecker
129 55f3f727 Andreas Kohlbecker
        setCompositionRoot(mainLayout);
130 0e7a9b4f Andreas Kohlbecker
131
        fieldGroup = new BeanFieldGroup<>(dtoType);
132
        fieldGroup.addCommitHandler(new SaveHandler());
133
134 fcba7787 Andreas Kohlbecker
        toolBar.addStyleName(ValoTheme.WINDOW_TOP_TOOLBAR);
135
        toolBar.setWidth(100, Unit.PERCENTAGE);
136
        toolBarButtonGroup.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
137
        toolBarButtonGroup.setWidthUndefined();
138
        toolBar.addComponent(toolBarButtonGroup);
139
        toolBar.setVisible(false);
140
141 0e7a9b4f Andreas Kohlbecker
        fieldLayout = layout;
142
        fieldLayout.setWidthUndefined();
143
        if(fieldLayout instanceof AbstractOrderedLayout){
144
            ((AbstractOrderedLayout)fieldLayout).setSpacing(true);
145
        }
146 fcf1cf06 Andreas Kohlbecker
        if(MarginHandler.class.isAssignableFrom(fieldLayout.getClass())){
147
            ((MarginHandler)fieldLayout).setMargin(new MarginInfo(false, true, true, true));
148
        }
149 0e7a9b4f Andreas Kohlbecker
150
        buttonLayout = new HorizontalLayout();
151
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
152
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
153
        buttonLayout.setSpacing(true);
154
155
        save = new Button("Save", FontAwesome.SAVE);
156
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
157 837c73ee Andreas Kohlbecker
        save.addClickListener(e -> save());
158 0e7a9b4f Andreas Kohlbecker
159 589e5b35 Andreas Kohlbecker
        cancel = new Button("Cancel", FontAwesome.REMOVE);
160 837c73ee Andreas Kohlbecker
        cancel.addClickListener(e -> cancel());
161 0e7a9b4f Andreas Kohlbecker
162 589e5b35 Andreas Kohlbecker
        delete = new Button("Delete", FontAwesome.TRASH);
163 538800b4 Andreas Kohlbecker
        delete.setStyleName(ValoTheme.BUTTON_DANGER);
164
        delete.addClickListener(e -> delete());
165 1aa951e9 Andreas Kohlbecker
        delete.setVisible(false);
166 538800b4 Andreas Kohlbecker
167
        buttonLayout.addComponents(delete, save, cancel);
168 1aa951e9 Andreas Kohlbecker
        // delete is initially invisible, let save take all space
169
        buttonLayout.setExpandRatio(save, 1);
170 538800b4 Andreas Kohlbecker
        buttonLayout.setComponentAlignment(delete, Alignment.TOP_RIGHT);
171 0e7a9b4f Andreas Kohlbecker
        buttonLayout.setComponentAlignment(save, Alignment.TOP_RIGHT);
172
        buttonLayout.setComponentAlignment(cancel, Alignment.TOP_RIGHT);
173
174 07a39ef5 Andreas Kohlbecker
        statusMessageLabel.setWidthUndefined();
175
176
        mainLayout.addComponents(toolBar, fieldLayout, statusMessageLabel, buttonLayout);
177
        mainLayout.setComponentAlignment(statusMessageLabel, Alignment.BOTTOM_RIGHT);
178 fcba7787 Andreas Kohlbecker
        mainLayout.setComponentAlignment(toolBar, Alignment.TOP_RIGHT);
179 f1573a7b Andreas Kohlbecker
180
        updateToolBarVisibility();
181 0e7a9b4f Andreas Kohlbecker
    }
182
183
    protected VerticalLayout getMainLayout() {
184
        return mainLayout;
185
    }
186
187 65508125 Andreas Kohlbecker
    protected Layout getFieldLayout() {
188
        return fieldLayout;
189
    }
190
191
    /**
192
     * @return
193
     */
194
    private GridLayout gridLayout() {
195
        if(_gridLayoutCache == null){
196
            if(fieldLayout instanceof GridLayout){
197
                _gridLayoutCache = (GridLayout)fieldLayout;
198
            } else {
199
                throw new RuntimeException("The fieldlayout of this editor is not a GridLayout");
200
            }
201
        }
202
        return _gridLayoutCache;
203
    }
204
205 0e7a9b4f Andreas Kohlbecker
    @Override
206
    public void setReadOnly(boolean readOnly) {
207
        super.setReadOnly(readOnly);
208
        save.setVisible(!readOnly);
209 f3783332 Andreas Kohlbecker
        delete.setVisible(!readOnly);
210 0e7a9b4f Andreas Kohlbecker
        cancel.setCaption(readOnly ? "Close" : "Cancel");
211 4a579136 Andreas Kohlbecker
        recursiveReadonly(readOnly, (AbstractComponentContainer)getFieldLayout());
212
    }
213
214
    /**
215
     * @param readOnly
216
     * @param layout
217
     */
218
    protected void recursiveReadonly(boolean readOnly, AbstractComponentContainer layout) {
219
        for(Component c : layout){
220
            c.setReadOnly(readOnly);
221
            if(c instanceof AbstractComponentContainer){
222 07a39ef5 Andreas Kohlbecker
                recursiveReadonly(readOnly, (AbstractComponentContainer)c);
223 4a579136 Andreas Kohlbecker
            }
224
        }
225 0e7a9b4f Andreas Kohlbecker
    }
226
227 fcba7787 Andreas Kohlbecker
    /**
228
     * @return
229
     * @return
230
     */
231
    protected AbstractLayout getToolBar() {
232
        return toolBar;
233
    }
234
235
    /**
236
     * @return
237
     * @return
238
     */
239
    protected void toolBarAdd(Component c) {
240
        toolBar.addComponent(c, toolBar.getComponentIndex(toolBarButtonGroup) - 1);
241
        updateToolBarVisibility();
242
    }
243
244
    /**
245
     * @return
246
     * @return
247
     */
248
    protected void toolBarButtonGroupAdd(Component c) {
249
        toolBarButtonGroup.addComponent(c);
250
        updateToolBarVisibility();
251
    }
252
253
    /**
254
     * @return
255
     * @return
256
     */
257
    protected void toolBarButtonGroupRemove(Component c) {
258
        toolBarButtonGroup.removeComponent(c);
259
        updateToolBarVisibility();
260
    }
261
262
    /**
263
     *
264
     */
265
    private void updateToolBarVisibility() {
266 f1573a7b Andreas Kohlbecker
        boolean showToolbar = toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1;
267 fcba7787 Andreas Kohlbecker
        toolBar.setVisible(toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1);
268 f1573a7b Andreas Kohlbecker
        if(!showToolbar){
269
            mainLayout.setMargin(new MarginInfo(true, false, false, false));
270
        } else {
271
            mainLayout.setMargin(false);
272
        }
273 fcba7787 Andreas Kohlbecker
274
    }
275
276
    /**
277
     * The top tool-bar is initially invisible.
278
     *
279
     * @param visible
280
     */
281
    protected void setToolBarVisible(boolean visible){
282
        toolBar.setVisible(true);
283
    }
284
285 fcf1cf06 Andreas Kohlbecker
    /**
286
     * @return the isAdvancedMode
287
     */
288
    public boolean isAdvancedMode() {
289
        return isAdvancedMode;
290
    }
291
292
    /**
293
     * @param isAdvancedMode the isAdvancedMode to set
294
     */
295
    public void setAdvancedMode(boolean isAdvancedMode) {
296
        this.isAdvancedMode = isAdvancedMode;
297
        advancedModeComponents.forEach(c -> c.setVisible(isAdvancedMode));
298
    }
299
300
    public void setAdvancedModeEnabled(boolean activate){
301
        if(activate && advancedModeButton == null){
302
            advancedModeButton = new Button(FontAwesome.WRENCH); // FontAwesome.FLASK
303
            advancedModeButton.setIconAlternateText("Advanced mode");
304
            advancedModeButton.addStyleName(ValoTheme.BUTTON_TINY);
305
            toolBarButtonGroupAdd(advancedModeButton);
306
            advancedModeButton.addClickListener(e -> {
307
                setAdvancedMode(!isAdvancedMode);
308
                }
309
            );
310
311
        } else if(advancedModeButton != null) {
312
            toolBarButtonGroupRemove(advancedModeButton);
313
            advancedModeButton = null;
314
        }
315
    }
316
317
    public void registerAdvancedModeComponents(Component ... c){
318
        advancedModeComponents.addAll(Arrays.asList(c));
319
    }
320 fcba7787 Andreas Kohlbecker
321
322 0e7a9b4f Andreas Kohlbecker
    // ------------------------ event handler ------------------------ //
323
324
    private class SaveHandler implements CommitHandler {
325 e7bf18d2 Andreas Kohlbecker
326 0e7a9b4f Andreas Kohlbecker
        private static final long serialVersionUID = 2047223089707080659L;
327
328
        @Override
329
        public void preCommit(CommitEvent commitEvent) throws CommitException {
330 1af6dc9f Andreas Kohlbecker
            logger.debug("preCommit(), publishing EditorPreSaveEvent");
331 5ba148ae Andreas Kohlbecker
            // notify the presenter to start a transaction
332 be4a9789 Andreas Kohlbecker
            viewEventBus.publish(this, new EditorPreSaveEvent<DTO>(AbstractPopupEditor.this, getBean()));
333 0e7a9b4f Andreas Kohlbecker
        }
334
335
        @Override
336
        public void postCommit(CommitEvent commitEvent) throws CommitException {
337
            try {
338 a2cc9f8d Andreas Kohlbecker
                if(logger.isTraceEnabled()){
339
                    logger.trace("postCommit() publishing EditorSaveEvent for " + getBean().toString());
340
                }
341 5ba148ae Andreas Kohlbecker
                // notify the presenter to persist the bean and to commit the transaction
342 be4a9789 Andreas Kohlbecker
                viewEventBus.publish(this, new EditorSaveEvent<DTO>(AbstractPopupEditor.this, getBean()));
343 a2cc9f8d Andreas Kohlbecker
                if(logger.isTraceEnabled()){
344
                    logger.trace("postCommit() publishing DoneWithPopupEvent");
345
                }
346 0e7a9b4f Andreas Kohlbecker
                // notify the NavigationManagerBean to close the window and to dispose the view
347 be4a9789 Andreas Kohlbecker
                viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(AbstractPopupEditor.this, Reason.SAVE));
348 0e7a9b4f Andreas Kohlbecker
            } catch (Exception e) {
349 3d9a0098 Andreas Kohlbecker
                logger.error(e);
350 0e7a9b4f Andreas Kohlbecker
                throw new CommitException("Failed to store data to backend", e);
351
            }
352
        }
353
    }
354
355
    protected void addCommitHandler(CommitHandler commitHandler) {
356
        fieldGroup.addCommitHandler(commitHandler);
357
    }
358
359
360 837c73ee Andreas Kohlbecker
    /**
361
     * Cancel editing and discard all modifications.
362
     */
363
    @Override
364
    public void cancel() {
365 0e7a9b4f Andreas Kohlbecker
        fieldGroup.discard();
366 be4a9789 Andreas Kohlbecker
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.CANCEL));
367 0e7a9b4f Andreas Kohlbecker
    }
368
369 538800b4 Andreas Kohlbecker
    /**
370
     * @return
371
     */
372
    private void delete() {
373 be4a9789 Andreas Kohlbecker
        viewEventBus.publish(this, new EditorDeleteEvent<DTO>(this, fieldGroup.getItemDataSource().getBean()));
374
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.DELETE));
375 538800b4 Andreas Kohlbecker
    }
376
377 837c73ee Andreas Kohlbecker
    /**
378
     * Save the changes made in the editor.
379
     */
380
    private void save() {
381 0e7a9b4f Andreas Kohlbecker
        try {
382
            fieldGroup.commit();
383
        } catch (CommitException e) {
384 5ba148ae Andreas Kohlbecker
            fieldGroup.getFields().forEach(f -> ((AbstractField<?>)f).setValidationVisible(true));
385 e7bf18d2 Andreas Kohlbecker
            if(e.getCause() != null && e.getCause() instanceof FieldGroupInvalidValueException){
386
                FieldGroupInvalidValueException invalidValueException = (FieldGroupInvalidValueException)e.getCause();
387
                updateFieldNotifications(invalidValueException.getInvalidFields());
388
                Notification.show("The entered data in " + invalidValueException.getInvalidFields().size() + " fields is incomplete or invalid.");
389 6702c7e1 Andreas Kohlbecker
            } else if(e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof PermissionDeniedException){
390
                PermissionDeniedException permissionDeniedException = (PermissionDeniedException)e.getCause().getCause();
391
                Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
392 26de749f Andreas Kohlbecker
            } else {
393
//                Logger.getLogger(this.getClass()).error("Error saving", e);
394
//                Notification.show("Error saving", Type.ERROR_MESSAGE);
395
                throw new RuntimeException("Error saving", e);
396 e7bf18d2 Andreas Kohlbecker
            }
397 0e7a9b4f Andreas Kohlbecker
        }
398
    }
399
400 e7bf18d2 Andreas Kohlbecker
    /**
401
     * @param invalidFields
402
     */
403
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
404
        for(Field<?> f : invalidFields.keySet()){
405
            if(f instanceof AbstractField){
406
                String message = invalidFields.get(f).getHtmlMessage();
407
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
408
            }
409
        }
410
411
    }
412
413 0e7a9b4f Andreas Kohlbecker
    // ------------------------ field adding methods ------------------------ //
414
415 e7bf18d2 Andreas Kohlbecker
416 0e7a9b4f Andreas Kohlbecker
    protected TextField addTextField(String caption, String propertyId) {
417 ecb93813 Andreas Kohlbecker
        return addField(new TextFieldNFix(caption), propertyId);
418 0e7a9b4f Andreas Kohlbecker
    }
419
420 65508125 Andreas Kohlbecker
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
421
            int column2, int row2)
422
            throws OverlapsException, OutOfBoundsException {
423 ecb93813 Andreas Kohlbecker
        return addField(new TextFieldNFix(caption), propertyId, column1, row1, column2, row2);
424 65508125 Andreas Kohlbecker
    }
425
426
    protected TextField addTextField(String caption, String propertyId, int column, int row)
427
            throws OverlapsException, OutOfBoundsException {
428 ecb93813 Andreas Kohlbecker
        return addField(new TextFieldNFix(caption), propertyId, column, row);
429 65508125 Andreas Kohlbecker
    }
430
431 cd29f770 Andreas Kohlbecker
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
432
            int column2, int row2)
433
            throws OverlapsException, OutOfBoundsException {
434
435
        SwitchableTextField field = new SwitchableTextField(caption);
436
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
437
        addComponent(field, column1, row1, column2, row2);
438
        return field;
439
    }
440
441
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
442
            throws OverlapsException, OutOfBoundsException {
443
444
        SwitchableTextField field = new SwitchableTextField(caption);
445
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
446
        addComponent(field, column, row);
447
        return field;
448
    }
449
450 0e7a9b4f Andreas Kohlbecker
    protected PopupDateField addDateField(String caption, String propertyId) {
451
        return addField(new PopupDateField(caption), propertyId);
452
    }
453
454
    protected CheckBox addCheckBox(String caption, String propertyId) {
455
        return addField(new CheckBox(caption), propertyId);
456
    }
457
458 f1573a7b Andreas Kohlbecker
    protected CheckBox addCheckBox(String caption, String propertyId, int column, int row){
459
        return addField(new CheckBox(caption), propertyId, column, row);
460
    }
461
462 0e7a9b4f Andreas Kohlbecker
    protected <T extends Field> T addField(T field, String propertyId) {
463
        fieldGroup.bind(field, propertyId);
464 843f2ff8 Andreas Kohlbecker
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
465
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
466
        }
467 65508125 Andreas Kohlbecker
        addComponent(field);
468
        return field;
469
    }
470
471
    /**
472
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
473
     *
474
     * @param field
475
     *            the field to be added, not <code>null</code>.
476
     * @param propertyId
477
     * @param column
478
     *            the column index, starting from 0.
479
     * @param row
480
     *            the row index, starting from 0.
481
     * @throws OverlapsException
482
     *             if the new component overlaps with any of the components
483
     *             already in the grid.
484
     * @throws OutOfBoundsException
485
     *             if the cell is outside the grid area.
486
     */
487
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
488
            throws OverlapsException, OutOfBoundsException {
489
        fieldGroup.bind(field, propertyId);
490 843f2ff8 Andreas Kohlbecker
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
491
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
492
        }
493 65508125 Andreas Kohlbecker
        addComponent(field, column, row);
494
        return field;
495
    }
496
497
    /**
498
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
499
     *
500
     * @param field
501
     * @param propertyId
502
     * @param column1
503
     * @param row1
504
     * @param column2
505
     * @param row2
506
     * @return
507
     * @throws OverlapsException
508
     * @throws OutOfBoundsException
509
     */
510
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
511
            int column2, int row2)
512
            throws OverlapsException, OutOfBoundsException {
513 bc5816ee Andreas Kohlbecker
        if(propertyId != null){
514
            fieldGroup.bind(field, propertyId);
515 843f2ff8 Andreas Kohlbecker
            if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
516
                ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
517
            }
518 bc5816ee Andreas Kohlbecker
        }
519 65508125 Andreas Kohlbecker
        addComponent(field, column1, row1, column2, row2);
520 0e7a9b4f Andreas Kohlbecker
        return field;
521
    }
522
523 793a7b67 Andreas Kohlbecker
    protected Field<?> getField(Object propertyId){
524
        return fieldGroup.getField(propertyId);
525
    }
526
527 af10794f Andreas Kohlbecker
    public PropertyIdPath boundPropertyIdPath(Field<?> field){
528
529
        PropertyIdPath propertyIdPath = null;
530
        Object propertyId = fieldGroup.getPropertyId(field);
531
532
        if(propertyId == null){
533
            // not found in the editor field group. Maybe the field is bound to a nested fieldgroup?
534
            // 1. find the NestedFieldGroup implementations from the field up to the editor
535
            logger.setLevel(Level.DEBUG);
536
            PropertyIdPath nestedPropertyIds = new PropertyIdPath();
537
            Field parentField = field;
538
            HasComponents parentComponent = parentField.getParent();
539
            logger.debug("field: " + parentField.getClass().getSimpleName());
540
            while(parentComponent != null){
541
                logger.debug("parentComponent: " + parentComponent.getClass().getSimpleName());
542
                if(NestedFieldGroup.class.isAssignableFrom(parentComponent.getClass()) && AbstractField.class.isAssignableFrom(parentComponent.getClass())){
543
                    Object propId = ((NestedFieldGroup)parentComponent).getFieldGroup().getPropertyId(parentField);
544
                    if(propId != null){
545
                        logger.debug("propId: " + propId.toString());
546
                        nestedPropertyIds.addParent(propId);
547
                    }
548
                    logger.debug("parentField: " + parentField.getClass().getSimpleName());
549
                    parentField = (Field)parentComponent;
550
                } else if(parentComponent == this) {
551
                    // we reached the editor itself
552
                    Object propId = fieldGroup.getPropertyId(parentField);
553
                    if(propId != null){
554
                        logger.debug("propId: " + propId.toString());
555
                        nestedPropertyIds.addParent(propId);
556
                    }
557
                    propertyIdPath = nestedPropertyIds;
558
                    break;
559
                }
560
                parentComponent = parentComponent.getParent();
561
            }
562
            // 2. check the NestedFieldGroup binding the field is direct or indirect child component of the editor
563
//            NO lONGER NEEDED
564
//            parentComponent = parentField.getParent(); // get component containing the last parent field found
565
//            while(true){
566
//                if(parentComponent == getFieldLayout()){
567
//                    propertyIdPath = nestedPropertyIds;
568
//                    break;
569
//                }
570
//                parentComponent = parentComponent.getParent();
571
//            }
572
        } else {
573
            propertyIdPath = new PropertyIdPath(propertyId);
574
        }
575
        return propertyIdPath;
576
    }
577
578 0e7a9b4f Andreas Kohlbecker
    protected void addComponent(Component component) {
579
        fieldLayout.addComponent(component);
580 74f4344f Andreas Kohlbecker
        applyDefaultComponentStyles(component);
581 0e7a9b4f Andreas Kohlbecker
    }
582
583 cfabbeb0 Andreas Kohlbecker
    protected void bindField(Field field, String propertyId){
584
        fieldGroup.bind(field, propertyId);
585
    }
586
587 65508125 Andreas Kohlbecker
    /**
588
     * @param component
589
     */
590
    public void applyDefaultComponentStyles(Component component) {
591 74f4344f Andreas Kohlbecker
        component.addStyleName(getDefaultComponentStyles());
592 65508125 Andreas Kohlbecker
    }
593
594
    protected abstract String getDefaultComponentStyles();
595
596
    /**
597
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
598
     * <p>
599
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
600
     * the area.) End coordinates (SouthEast corner of the area) are the same as
601
     * column1,row1. The coordinates are zero-based. Component width and height
602
     * is 1.
603
     *
604
     * @param component
605
     *            the component to be added, not <code>null</code>.
606
     * @param column
607
     *            the column index, starting from 0.
608
     * @param row
609
     *            the row index, starting from 0.
610
     * @throws OverlapsException
611
     *             if the new component overlaps with any of the components
612
     *             already in the grid.
613
     * @throws OutOfBoundsException
614
     *             if the cell is outside the grid area.
615
     */
616
    public void addComponent(Component component, int column, int row)
617
            throws OverlapsException, OutOfBoundsException {
618
        applyDefaultComponentStyles(component);
619
        gridLayout().addComponent(component, column, row, column, row);
620
    }
621
622
    /**
623
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
624
     * <p>
625
     * Adds a component to the grid in the specified area. The area is defined
626
     * by specifying the upper left corner (column1, row1) and the lower right
627
     * corner (column2, row2) of the area. The coordinates are zero-based.
628
     * </p>
629
     *
630
     * <p>
631
     * If the area overlaps with any of the existing components already present
632
     * in the grid, the operation will fail and an {@link OverlapsException} is
633
     * thrown.
634
     * </p>
635
     *
636
     * @param component
637
     *            the component to be added, not <code>null</code>.
638
     * @param column1
639
     *            the column of the upper left corner of the area <code>c</code>
640
     *            is supposed to occupy. The leftmost column has index 0.
641
     * @param row1
642
     *            the row of the upper left corner of the area <code>c</code> is
643
     *            supposed to occupy. The topmost row has index 0.
644
     * @param column2
645
     *            the column of the lower right corner of the area
646
     *            <code>c</code> is supposed to occupy.
647
     * @param row2
648
     *            the row of the lower right corner of the area <code>c</code>
649
     *            is supposed to occupy.
650
     * @throws OverlapsException
651
     *             if the new component overlaps with any of the components
652
     *             already in the grid.
653
     * @throws OutOfBoundsException
654
     *             if the cells are outside the grid area.
655
     */
656
    public void addComponent(Component component, int column1, int row1,
657
            int column2, int row2)
658
            throws OverlapsException, OutOfBoundsException {
659
        applyDefaultComponentStyles(component);
660
        gridLayout().addComponent(component, column1, row1, column2, row2);
661
    }
662
663 07a39ef5 Andreas Kohlbecker
    public void setSaveButtonEnabled(boolean enabled){
664
        save.setEnabled(enabled);
665
    }
666 65508125 Andreas Kohlbecker
667 1aa951e9 Andreas Kohlbecker
    public void withDeleteButton(boolean withDelete){
668
669
        if(withDelete){
670
            buttonLayout.setExpandRatio(save, 0);
671
            buttonLayout.setExpandRatio(delete, 1);
672
        } else {
673
            buttonLayout.setExpandRatio(save, 1);
674
            buttonLayout.setExpandRatio(delete, 0);
675
        }
676
        delete.setVisible(withDelete);
677
    }
678
679 07a39ef5 Andreas Kohlbecker
    public boolean addStatusMessage(String message){
680
        boolean returnVal = statusMessages.add(message);
681
        updateStatusLabel();
682
        return returnVal;
683
    }
684
685
    public boolean removeStatusMessage(String message){
686
        boolean returnVal = statusMessages.remove(message);
687
        updateStatusLabel();
688
        return returnVal;
689
    }
690
691
    /**
692
     *
693
     */
694
    private void updateStatusLabel() {
695
        String text = "";
696
        for(String s : statusMessages){
697
            text += s + " ";
698
        }
699
        statusMessageLabel.setValue(text);
700
        statusMessageLabel.setVisible(!text.isEmpty());
701
        statusMessageLabel.addStyleName(ValoTheme.LABEL_COLORED);
702
    }
703 65508125 Andreas Kohlbecker
704 0e7a9b4f Andreas Kohlbecker
    // ------------------------ data binding ------------------------ //
705
706
    protected void bindDesign(Component component) {
707
        fieldLayout.removeAllComponents();
708
        fieldGroup.bindMemberFields(component);
709
        fieldLayout.addComponent(component);
710
    }
711
712 c46954e3 Andreas Kohlbecker
713 fe785c1e Andreas Kohlbecker
    public final void loadInEditor(Object identifier) {
714
715
        DTO beanToEdit = getPresenter().loadBeanById(identifier);
716 d6bdb890 Andreas Kohlbecker
        fieldGroup.setItemDataSource(beanToEdit);
717 4a1bb0e9 Andreas Kohlbecker
        afterItemDataSourceSet();
718 44b9dbd2 Andreas Kohlbecker
        getPresenter().adaptToUserPermission(beanToEdit);
719 02ec8d6b Andreas Kohlbecker
        isBeanLoaded = true;
720 4a1bb0e9 Andreas Kohlbecker
    }
721
722 333b3e62 Andreas Kohlbecker
    /**
723
     * Passes the beanInstantiator to the presenter method {@link AbstractEditorPresenter#setBeanInstantiator(BeanInstantiator)}
724
     *
725
     * @param beanInstantiator
726
     */
727 21c1abac Andreas Kohlbecker
    public final void setBeanInstantiator(BeanInstantiator<DTO> beanInstantiator) {
728
        getPresenter().setBeanInstantiator(beanInstantiator);
729
    }
730
731 c46954e3 Andreas Kohlbecker
    /**
732 a2cc9f8d Andreas Kohlbecker
     * Returns the bean contained in the itemDatasource of the fieldGroup.
733 c46954e3 Andreas Kohlbecker
     *
734
     * @return
735
     */
736
    public DTO getBean() {
737 9da64e4a Andreas Kohlbecker
        if(fieldGroup.getItemDataSource() != null){
738
            return fieldGroup.getItemDataSource().getBean();
739
740
        }
741
        return null;
742 c46954e3 Andreas Kohlbecker
    }
743
744 02ec8d6b Andreas Kohlbecker
    /**
745
     * @return true once the bean has been loaded indicating that all fields have
746
     *   been setup configured so that the editor is ready for use.
747
     */
748
    public boolean isBeanLoaded() {
749
        return isBeanLoaded;
750
    }
751
752 a2cc9f8d Andreas Kohlbecker
    /**
753
     * This method should only be used by the presenter of this view
754
     *
755
     * @param bean
756
     */
757
    protected void updateItemDataSource(DTO bean) {
758
        fieldGroup.getItemDataSource().setBean(bean);
759
    }
760
761 4a1bb0e9 Andreas Kohlbecker
    /**
762 3a8b638c Andreas Kohlbecker
     * This method is called after setting the item data source whereby the
763
     * {@link FieldGroup#configureField(Field<?> field)} method will be called.
764 4a1bb0e9 Andreas Kohlbecker
     * In this method all fields are set to default states defined for the fieldGroup.
765
     * <p>
766 b6ea271c Andreas Kohlbecker
     * You can now implement this method if you need to modify the state or value of individual fields.
767 4a1bb0e9 Andreas Kohlbecker
     */
768
    protected void afterItemDataSourceSet() {
769 0e7a9b4f Andreas Kohlbecker
    }
770 837c73ee Andreas Kohlbecker
771
    // ------------------------ issue related temporary solutions --------------------- //
772
    /**
773 3a8b638c Andreas Kohlbecker
     * Publicly accessible equivalent to getPreseneter(), needed for
774
     * managing the presenter listeners.
775
     * <p>
776
     * TODO: refactor the presenter listeners management to get rid of this method
777 837c73ee Andreas Kohlbecker
     *
778
     * @return
779 3a8b638c Andreas Kohlbecker
     * @deprecated marked deprecated to emphasize on the special character of this method
780 b6ea271c Andreas Kohlbecker
     *    which should only be used internally see #6673
781 837c73ee Andreas Kohlbecker
     */
782
    @Deprecated
783
    public P presenter() {
784
        return getPresenter();
785
    }
786 f3783332 Andreas Kohlbecker
787 e8674a36 Andreas Kohlbecker
    /**
788
     * Returns the context of editor actions for this editor.
789
     * The context submitted with {@link #setParentContext(Stack)} will be updated
790
     * to represent the current context.
791
     *
792
     * @return the context
793
     */
794
    public Stack<EditorActionContext> getEditorActionContext() {
795
        if(!isContextUpdated){
796
            if(getBean() == null){
797
                throw new RuntimeException("getContext() is only possible after the bean is loaded");
798
            }
799
            context.push(new AbstractEditorAction.EditorActionContext(getBean(), this));
800
            isContextUpdated = true;
801
        }
802
        return context;
803
    }
804
805
    /**
806
     * Set the context of editor actions parent to this editor
807
     *
808
     * @param context the context to set
809
     */
810
    public void setParentEditorActionContext(Stack<EditorActionContext> context) {
811 7b9ab0e4 Andreas Kohlbecker
        if(context != null){
812
            this.context.addAll(context);
813
        }
814 e8674a36 Andreas Kohlbecker
    }
815
816 0e7a9b4f Andreas Kohlbecker
}