Project

General

Profile

Download (34.7 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.ArrayList;
12
import java.util.Arrays;
13
import java.util.HashSet;
14
import java.util.List;
15
import java.util.Map;
16
import java.util.Set;
17
import java.util.Stack;
18

    
19
import org.apache.log4j.Level;
20
import org.apache.log4j.Logger;
21
import org.vaadin.spring.events.EventScope;
22

    
23
import com.vaadin.data.Validator.InvalidValueException;
24
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
import com.vaadin.data.fieldgroup.FieldGroup.FieldGroupInvalidValueException;
29
import com.vaadin.server.AbstractErrorMessage.ContentMode;
30
import com.vaadin.server.ErrorMessage.ErrorLevel;
31
import com.vaadin.server.FontAwesome;
32
import com.vaadin.server.GenericFontIcon;
33
import com.vaadin.server.UserError;
34
import com.vaadin.shared.ui.MarginInfo;
35
import com.vaadin.ui.AbstractComponentContainer;
36
import com.vaadin.ui.AbstractField;
37
import com.vaadin.ui.AbstractLayout;
38
import com.vaadin.ui.AbstractOrderedLayout;
39
import com.vaadin.ui.Alignment;
40
import com.vaadin.ui.Button;
41
import com.vaadin.ui.Button.ClickListener;
42
import com.vaadin.ui.CheckBox;
43
import com.vaadin.ui.Component;
44
import com.vaadin.ui.CssLayout;
45
import com.vaadin.ui.Field;
46
import com.vaadin.ui.GridLayout;
47
import com.vaadin.ui.GridLayout.OutOfBoundsException;
48
import com.vaadin.ui.GridLayout.OverlapsException;
49
import com.vaadin.ui.HasComponents;
50
import com.vaadin.ui.HorizontalLayout;
51
import com.vaadin.ui.Label;
52
import com.vaadin.ui.Layout;
53
import com.vaadin.ui.Layout.MarginHandler;
54
import com.vaadin.ui.Notification;
55
import com.vaadin.ui.Notification.Type;
56
import com.vaadin.ui.PopupDateField;
57
import com.vaadin.ui.TextField;
58
import com.vaadin.ui.UI;
59
import com.vaadin.ui.VerticalLayout;
60
import com.vaadin.ui.themes.ValoTheme;
61

    
62
import eu.etaxonomy.cdm.database.PermissionDeniedException;
63
import eu.etaxonomy.cdm.vaadin.component.TextFieldNFix;
64
import eu.etaxonomy.cdm.vaadin.component.dialog.ContinueAlternativeCancelDialog;
65
import eu.etaxonomy.cdm.vaadin.event.EditorActionContext;
66
import eu.etaxonomy.cdm.vaadin.event.EditorActionContextFormat;
67
import eu.etaxonomy.cdm.vaadin.event.EditorActionContextFormatter;
68
import eu.etaxonomy.vaadin.component.NestedFieldGroup;
69
import eu.etaxonomy.vaadin.component.SwitchableTextField;
70
import eu.etaxonomy.vaadin.event.FieldReplaceEvent;
71
import eu.etaxonomy.vaadin.mvp.event.EditorDeleteEvent;
72
import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
73
import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
74
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
75
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
76
import eu.etaxonomy.vaadin.util.PropertyIdPath;
77

    
78
/**
79
 *
80
 * Optional with a delete button which can be enabled with {@link #withDeleteButton(boolean)}
81
 *
82
 * @author a.kohlbecker
83
 * @since Apr 5, 2017
84
 *
85
 */
86
public abstract class AbstractPopupEditor<DTO extends Object, P extends AbstractEditorPresenter<DTO, ? extends ApplicationView>>
87
    extends AbstractPopupView<P> {
88

    
89
    /**
90
     *
91
     */
92
    private static final String READ_ONLY_MESSAGE_TEXT = "The editor is in read-only mode. Your authorities are not sufficient to edit this data.";
93

    
94
    public static final Logger logger = Logger.getLogger(AbstractPopupEditor.class);
95

    
96
    private BeanFieldGroup<DTO> fieldGroup;
97

    
98
    private VerticalLayout mainLayout;
99

    
100
    private Layout fieldLayout;
101

    
102
    private HorizontalLayout buttonLayout;
103

    
104
    private Button save;
105

    
106
    private Button cancel;
107

    
108
    private Button delete;
109

    
110
    private CssLayout toolBar = new CssLayout();
111

    
112
    private CssLayout toolBarButtonGroup = new CssLayout();
113

    
114
    private Label contextBreadcrumbsLabel = new Label();
115

    
116
    private Label statusMessageLabel = new Label();
117

    
118
    Set<String> statusMessages = new HashSet<>();
119

    
120
    private GridLayout _gridLayoutCache;
121

    
122
    private boolean isBeanLoaded;
123

    
124
    private Stack<EditorActionContext> context = new Stack<EditorActionContext>();
125

    
126
    private boolean isContextUpdated;
127

    
128
    private boolean isAdvancedMode = false;
129

    
130
    protected List<Component> advancedModeComponents = new ArrayList<>();
131

    
132
    private Button advancedModeButton;
133

    
134
    private EditorFormConfigurator<? extends AbstractPopupEditor<DTO, P>> editorComponentsConfigurator;
135

    
136
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
137

    
138
        mainLayout = new VerticalLayout();
139
        // IMPORTANT: mainLayout must be set to full size otherwise the
140
        // popup window may have problems with automatic resizing of its
141
        // content.
142
        mainLayout.setSizeFull();
143

    
144
        setCompositionRoot(mainLayout);
145

    
146
        fieldGroup = new BeanFieldGroup<>(dtoType);
147
        fieldGroup.addCommitHandler(new SaveHandler());
148

    
149
        toolBar.addStyleName(ValoTheme.WINDOW_TOP_TOOLBAR);
150
        toolBar.setWidth(100, Unit.PERCENTAGE);
151
        contextBreadcrumbsLabel.setId("context-breadcrumbs");
152
        contextBreadcrumbsLabel.setWidthUndefined();
153
        contextBreadcrumbsLabel.setContentMode(com.vaadin.shared.ui.label.ContentMode.HTML);
154
        toolBar.addComponent(contextBreadcrumbsLabel);
155
        toolBarButtonGroup.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
156
        toolBarButtonGroup.setWidthUndefined();
157
        toolBar.addComponent(toolBarButtonGroup);
158
        toolBar.setVisible(false);
159

    
160
        fieldLayout = layout;
161
        fieldLayout.setWidthUndefined();
162
        if(fieldLayout instanceof AbstractOrderedLayout){
163
            ((AbstractOrderedLayout)fieldLayout).setSpacing(true);
164
        }
165
        if(MarginHandler.class.isAssignableFrom(fieldLayout.getClass())){
166
            ((MarginHandler)fieldLayout).setMargin(new MarginInfo(false, true, true, true));
167
        }
168

    
169
        buttonLayout = new HorizontalLayout();
170
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
171
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
172
        buttonLayout.setSpacing(true);
173

    
174
        save = new Button("Save", FontAwesome.SAVE);
175
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
176
        save.addClickListener(e -> save());
177

    
178
        cancel = new Button("Cancel", FontAwesome.REMOVE);
179
        cancel.addClickListener(e -> cancelEditorDialog());
180

    
181
        delete = new Button("Delete", FontAwesome.TRASH);
182
        delete.setStyleName(ValoTheme.BUTTON_DANGER);
183
        delete.addClickListener(e -> delete());
184
        delete.setVisible(false);
185

    
186
        buttonLayout.addComponents(delete, save, cancel);
187
        // delete is initially invisible, let save take all space
188
        buttonLayout.setExpandRatio(save, 1);
189
        buttonLayout.setComponentAlignment(delete, Alignment.TOP_RIGHT);
190
        buttonLayout.setComponentAlignment(save, Alignment.TOP_RIGHT);
191
        buttonLayout.setComponentAlignment(cancel, Alignment.TOP_RIGHT);
192

    
193
        statusMessageLabel.setWidthUndefined();
194

    
195
        mainLayout.addComponents(toolBar, fieldLayout, statusMessageLabel, buttonLayout);
196
        mainLayout.setComponentAlignment(statusMessageLabel, Alignment.BOTTOM_RIGHT);
197
        mainLayout.setComponentAlignment(toolBar, Alignment.TOP_RIGHT);
198

    
199
        updateToolBarVisibility();
200
    }
201

    
202
    protected VerticalLayout getMainLayout() {
203
        return mainLayout;
204
    }
205

    
206
    protected Layout getFieldLayout() {
207
        return fieldLayout;
208
    }
209

    
210
    /**
211
     * @return
212
     */
213
    private GridLayout gridLayout() {
214
        if(_gridLayoutCache == null){
215
            if(fieldLayout instanceof GridLayout){
216
                _gridLayoutCache = (GridLayout)fieldLayout;
217
            } else {
218
                throw new RuntimeException("The fieldlayout of this editor is not a GridLayout");
219
            }
220
        }
221
        return _gridLayoutCache;
222
    }
223

    
224
    @Override
225
    public void setReadOnly(boolean readOnly) {
226
        super.setReadOnly(readOnly);
227
        if(readOnly){
228
            statusMessageLabel.setValue(READ_ONLY_MESSAGE_TEXT);
229
            statusMessageLabel.addStyleName(ValoTheme.LABEL_COLORED);
230
        } else {
231
            statusMessageLabel.setValue(null);
232
        }
233
        statusMessageLabel.setVisible(readOnly);
234
        save.setVisible(!readOnly);
235
        delete.setVisible(!readOnly);
236
        cancel.setCaption(readOnly ? "Close" : "Cancel");
237
        recursiveReadonly(readOnly, (AbstractComponentContainer)getFieldLayout());
238
    }
239

    
240
    /**
241
     * @param readOnly
242
     * @param layout
243
     */
244
    protected void recursiveReadonly(boolean readOnly, AbstractComponentContainer layout) {
245
        for(Component c : layout){
246
            c.setReadOnly(readOnly);
247
            if(c instanceof AbstractComponentContainer){
248
                recursiveReadonly(readOnly, (AbstractComponentContainer)c);
249
            }
250
        }
251
    }
252

    
253
    /**
254
     * @return
255
     * @return
256
     */
257
    protected AbstractLayout getToolBar() {
258
        return toolBar;
259
    }
260

    
261
    /**
262
     * @return
263
     * @return
264
     */
265
    protected void toolBarAdd(Component c) {
266
        toolBar.addComponent(c, toolBar.getComponentIndex(toolBarButtonGroup) - 1);
267
        updateToolBarVisibility();
268
    }
269

    
270
    /**
271
     * @return
272
     * @return
273
     */
274
    protected void toolBarButtonGroupAdd(Component c) {
275
        toolBarButtonGroup.addComponent(c);
276
        updateToolBarVisibility();
277
    }
278

    
279
    /**
280
     * @return
281
     * @return
282
     */
283
    protected void toolBarButtonGroupRemove(Component c) {
284
        toolBarButtonGroup.removeComponent(c);
285
        updateToolBarVisibility();
286
    }
287

    
288
    /**
289
     *
290
     */
291
    private void updateToolBarVisibility() {
292
        boolean showToolbar = toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1;
293
        toolBar.setVisible(toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1);
294
        if(!showToolbar){
295
            mainLayout.setMargin(new MarginInfo(true, false, false, false));
296
        } else {
297
            mainLayout.setMargin(false);
298
        }
299

    
300
    }
301

    
302
    /**
303
     * The top tool-bar is initially invisible.
304
     *
305
     * @param visible
306
     */
307
    protected void setToolBarVisible(boolean visible){
308
        toolBar.setVisible(true);
309
    }
310

    
311
    /**
312
     * @return the isAdvancedMode
313
     */
314
    public boolean isAdvancedMode() {
315
        return isAdvancedMode;
316
    }
317

    
318
    /**
319
     * @param isAdvancedMode the isAdvancedMode to set
320
     */
321
    public void setAdvancedMode(boolean isAdvancedMode) {
322
        this.isAdvancedMode = isAdvancedMode;
323
        advancedModeComponents.forEach(c -> c.setVisible(isAdvancedMode));
324
    }
325

    
326
    public void setAdvancedModeEnabled(boolean activate){
327
        if(activate && advancedModeButton == null){
328
            advancedModeButton = new Button(FontAwesome.WRENCH); // FontAwesome.FLASK
329
            advancedModeButton.setIconAlternateText("Advanced mode");
330
            advancedModeButton.addStyleName(ValoTheme.BUTTON_TINY);
331
            toolBarButtonGroupAdd(advancedModeButton);
332
            advancedModeButton.addClickListener(e -> {
333
                setAdvancedMode(!isAdvancedMode);
334
                }
335
            );
336

    
337
        } else if(advancedModeButton != null) {
338
            toolBarButtonGroupRemove(advancedModeButton);
339
            advancedModeButton = null;
340
        }
341
    }
342

    
343
    public void registerAdvancedModeComponents(Component ... c){
344
        advancedModeComponents.addAll(Arrays.asList(c));
345
    }
346

    
347

    
348
    // ------------------------ event handler ------------------------ //
349

    
350
    private class SaveHandler implements CommitHandler {
351

    
352
        private static final long serialVersionUID = 2047223089707080659L;
353

    
354
        @Override
355
        public void preCommit(CommitEvent commitEvent) throws CommitException {
356
            logger.debug("preCommit(), publishing EditorPreSaveEvent");
357
            // notify the presenter to start a transaction
358
            viewEventBus.publish(this, new EditorPreSaveEvent<DTO>(AbstractPopupEditor.this, getBean()));
359
        }
360

    
361
        @Override
362
        public void postCommit(CommitEvent commitEvent) throws CommitException {
363
            try {
364
                if(logger.isTraceEnabled()){
365
                    logger.trace("postCommit() publishing EditorSaveEvent for " + getBean().toString());
366
                }
367
                // notify the presenter to persist the bean and to commit the transaction
368
                viewEventBus.publish(this, new EditorSaveEvent<DTO>(AbstractPopupEditor.this, getBean()));
369
                if(logger.isTraceEnabled()){
370
                    logger.trace("postCommit() publishing DoneWithPopupEvent");
371
                }
372
                // notify the NavigationManagerBean to close the window and to dispose the view
373
                viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(AbstractPopupEditor.this, Reason.SAVE));
374
            } catch (Exception e) {
375
                logger.error(e);
376
                throw new CommitException("Failed to store data to backend", e);
377
            }
378
        }
379
    }
380

    
381
    protected void addCommitHandler(CommitHandler commitHandler) {
382
        fieldGroup.addCommitHandler(commitHandler);
383
    }
384

    
385
    protected void cancelEditorDialog(){
386

    
387
        if(fieldGroup.isModified()){
388

    
389
            ContinueAlternativeCancelDialog editorModifiedDialog = new ContinueAlternativeCancelDialog(
390
                    "Cancel editor",
391
                    "<p>The editor has been modified.<br>Do you want to save your changes or discard them?<p>",
392
                    "Discard",
393
                    "Save");
394
            ClickListener saveListener = e -> {editorModifiedDialog.close(); save();};
395
            ClickListener discardListener = e -> {editorModifiedDialog.close(); cancel();};
396
            ClickListener cancelListener = e -> editorModifiedDialog.close();
397
            editorModifiedDialog.addAlternativeClickListener(saveListener);
398
            editorModifiedDialog.addContinueClickListener(discardListener);
399
            editorModifiedDialog.addCancelClickListener(cancelListener);
400

    
401
            UI.getCurrent().addWindow(editorModifiedDialog);
402
        } else {
403
            cancel();
404
        }
405
    }
406

    
407

    
408
    /**
409
     * Cancel editing and discard all modifications.
410
     */
411
    @Override
412
    public void cancel() {
413
        fieldGroup.discard();
414
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.CANCEL));
415
    }
416

    
417
    /**
418
     * @return
419
     */
420
    private void delete() {
421
        viewEventBus.publish(this, new EditorDeleteEvent<DTO>(this, fieldGroup.getItemDataSource().getBean()));
422
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.DELETE));
423
    }
424

    
425
    /**
426
     * Save the changes made in the editor.
427
     */
428
    private void save() {
429
        try {
430
            fieldGroup.commit();
431
        } catch (CommitException e) {
432
            fieldGroup.getFields().forEach(f -> ((AbstractField<?>)f).setValidationVisible(true));
433
            Throwable cause = e.getCause();
434
            while(cause != null) {
435
                if(cause instanceof FieldGroupInvalidValueException){
436
                    FieldGroupInvalidValueException invalidValueException = (FieldGroupInvalidValueException)cause;
437
                    updateFieldNotifications(invalidValueException.getInvalidFields());
438
                    int invalidFieldsCount = invalidValueException.getInvalidFields().size();
439
                    Notification.show("The entered data in " + invalidFieldsCount + " field" + (invalidFieldsCount > 1 ? "s": "") + " is incomplete or invalid.");
440
                    break;
441
                } else if(cause instanceof PermissionDeniedException){
442
                    PermissionDeniedException permissionDeniedException = (PermissionDeniedException)cause;
443
                    Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
444
                    break;
445
                }
446
                cause = cause.getCause();
447
            }
448
            if(cause == null){
449
                // no known exception type found
450
                throw new PopupEditorException("Error saving popup editor", this, e);
451
            }
452

    
453
        }
454
    }
455

    
456
    /**
457
     * @param invalidFields
458
     */
459
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
460
        for(Field<?> f : invalidFields.keySet()){
461
            if(f instanceof AbstractField){
462
                String message = invalidFields.get(f).getHtmlMessage();
463
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
464
            }
465
        }
466

    
467
    }
468

    
469
    // ------------------------ field adding methods ------------------------ //
470

    
471

    
472
    protected TextField addTextField(String caption, String propertyId) {
473
        return addField(new TextFieldNFix(caption), propertyId);
474
    }
475

    
476
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
477
            int column2, int row2)
478
            throws OverlapsException, OutOfBoundsException {
479
        return addField(new TextFieldNFix(caption), propertyId, column1, row1, column2, row2);
480
    }
481

    
482
    protected TextField addTextField(String caption, String propertyId, int column, int row)
483
            throws OverlapsException, OutOfBoundsException {
484
        return addField(new TextFieldNFix(caption), propertyId, column, row);
485
    }
486

    
487
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
488
            int column2, int row2)
489
            throws OverlapsException, OutOfBoundsException {
490

    
491
        SwitchableTextField field = new SwitchableTextField(caption);
492
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
493
        addComponent(field, column1, row1, column2, row2);
494
        return field;
495
    }
496

    
497
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
498
            throws OverlapsException, OutOfBoundsException {
499

    
500
        SwitchableTextField field = new SwitchableTextField(caption);
501
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
502
        addComponent(field, column, row);
503
        return field;
504
    }
505

    
506
    protected PopupDateField addDateField(String caption, String propertyId) {
507
        return addField(new PopupDateField(caption), propertyId);
508
    }
509

    
510
    protected CheckBox addCheckBox(String caption, String propertyId) {
511
        return addField(new CheckBox(caption), propertyId);
512
    }
513

    
514
    protected CheckBox addCheckBox(String caption, String propertyId, int column, int row){
515
        return addField(new CheckBox(caption), propertyId, column, row);
516
    }
517

    
518
    protected <T extends Field> T addField(T field, String propertyId) {
519
        fieldGroup.bind(field, propertyId);
520
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
521
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
522
        }
523
        addComponent(field);
524
        return field;
525
    }
526

    
527
    /**
528
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
529
     *
530
     * @param field
531
     *            the field to be added, not <code>null</code>.
532
     * @param propertyId
533
     * @param column
534
     *            the column index, starting from 0.
535
     * @param row
536
     *            the row index, starting from 0.
537
     * @throws OverlapsException
538
     *             if the new component overlaps with any of the components
539
     *             already in the grid.
540
     * @throws OutOfBoundsException
541
     *             if the cell is outside the grid area.
542
     */
543
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
544
            throws OverlapsException, OutOfBoundsException {
545
        fieldGroup.bind(field, propertyId);
546
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
547
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
548
        }
549
        addComponent(field, column, row);
550
        return field;
551
    }
552

    
553
    /**
554
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
555
     *
556
     * @param field
557
     * @param propertyId
558
     * @param column1
559
     * @param row1
560
     * @param column2
561
     * @param row2
562
     * @return
563
     * @throws OverlapsException
564
     * @throws OutOfBoundsException
565
     */
566
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
567
            int column2, int row2)
568
            throws OverlapsException, OutOfBoundsException {
569
        if(propertyId != null){
570
            fieldGroup.bind(field, propertyId);
571
            if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
572
                ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
573
            }
574
        }
575
        addComponent(field, column1, row1, column2, row2);
576
        return field;
577
    }
578

    
579
    protected Field<?> getField(Object propertyId){
580
        return fieldGroup.getField(propertyId);
581
    }
582

    
583
    public PropertyIdPath boundPropertyIdPath(Field<?> field){
584

    
585
        PropertyIdPath propertyIdPath = null;
586
        Object propertyId = fieldGroup.getPropertyId(field);
587

    
588
        if(propertyId == null){
589
            // not found in the editor field group. Maybe the field is bound to a nested fieldgroup?
590
            // 1. find the NestedFieldGroup implementations from the field up to the editor
591
            logger.setLevel(Level.DEBUG);
592
            PropertyIdPath nestedPropertyIds = new PropertyIdPath();
593
            Field parentField = field;
594
            HasComponents parentComponent = parentField.getParent();
595
            logger.debug("field: " + parentField.getClass().getSimpleName());
596
            while(parentComponent != null){
597
                logger.debug("parentComponent: " + parentComponent.getClass().getSimpleName());
598
                if(NestedFieldGroup.class.isAssignableFrom(parentComponent.getClass()) && AbstractField.class.isAssignableFrom(parentComponent.getClass())){
599
                    Object propId = ((NestedFieldGroup)parentComponent).getFieldGroup().getPropertyId(parentField);
600
                    if(propId != null){
601
                        logger.debug("propId: " + propId.toString());
602
                        nestedPropertyIds.addParent(propId);
603
                    }
604
                    logger.debug("parentField: " + parentField.getClass().getSimpleName());
605
                    parentField = (Field)parentComponent;
606
                } else if(parentComponent == this) {
607
                    // we reached the editor itself
608
                    Object propId = fieldGroup.getPropertyId(parentField);
609
                    if(propId != null){
610
                        logger.debug("propId: " + propId.toString());
611
                        nestedPropertyIds.addParent(propId);
612
                    }
613
                    propertyIdPath = nestedPropertyIds;
614
                    break;
615
                }
616
                parentComponent = parentComponent.getParent();
617
            }
618
            // 2. check the NestedFieldGroup binding the field is direct or indirect child component of the editor
619
//            NO lONGER NEEDED
620
//            parentComponent = parentField.getParent(); // get component containing the last parent field found
621
//            while(true){
622
//                if(parentComponent == getFieldLayout()){
623
//                    propertyIdPath = nestedPropertyIds;
624
//                    break;
625
//                }
626
//                parentComponent = parentComponent.getParent();
627
//            }
628
        } else {
629
            propertyIdPath = new PropertyIdPath(propertyId);
630
        }
631
        return propertyIdPath;
632
    }
633

    
634
    protected void addComponent(Component component) {
635
        fieldLayout.addComponent(component);
636
        applyDefaultComponentStyles(component);
637
    }
638

    
639
    protected void bindField(Field field, String propertyId){
640
        fieldGroup.bind(field, propertyId);
641
    }
642

    
643
    protected void unbindField(Field field){
644
        fieldGroup.unbind(field);
645
    }
646

    
647
    /**
648
     * @param component
649
     */
650
    public void applyDefaultComponentStyles(Component component) {
651
        component.addStyleName(getDefaultComponentStyles());
652
    }
653

    
654
    protected abstract String getDefaultComponentStyles();
655

    
656
    /**
657
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
658
     * <p>
659
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
660
     * the area.) End coordinates (SouthEast corner of the area) are the same as
661
     * column1,row1. The coordinates are zero-based. Component width and height
662
     * is 1.
663
     *
664
     * @param component
665
     *            the component to be added, not <code>null</code>.
666
     * @param column
667
     *            the column index, starting from 0.
668
     * @param row
669
     *            the row index, starting from 0.
670
     * @throws OverlapsException
671
     *             if the new component overlaps with any of the components
672
     *             already in the grid.
673
     * @throws OutOfBoundsException
674
     *             if the cell is outside the grid area.
675
     */
676
    public void addComponent(Component component, int column, int row)
677
            throws OverlapsException, OutOfBoundsException {
678
        applyDefaultComponentStyles(component);
679
        gridLayout().addComponent(component, column, row, column, row);
680
    }
681

    
682
    /**
683
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
684
     * <p>
685
     * Adds a component to the grid in the specified area. The area is defined
686
     * by specifying the upper left corner (column1, row1) and the lower right
687
     * corner (column2, row2) of the area. The coordinates are zero-based.
688
     * </p>
689
     *
690
     * <p>
691
     * If the area overlaps with any of the existing components already present
692
     * in the grid, the operation will fail and an {@link OverlapsException} is
693
     * thrown.
694
     * </p>
695
     *
696
     * @param component
697
     *            the component to be added, not <code>null</code>.
698
     * @param column1
699
     *            the column of the upper left corner of the area <code>c</code>
700
     *            is supposed to occupy. The leftmost column has index 0.
701
     * @param row1
702
     *            the row of the upper left corner of the area <code>c</code> is
703
     *            supposed to occupy. The topmost row has index 0.
704
     * @param column2
705
     *            the column of the lower right corner of the area
706
     *            <code>c</code> is supposed to occupy.
707
     * @param row2
708
     *            the row of the lower right corner of the area <code>c</code>
709
     *            is supposed to occupy.
710
     * @throws OverlapsException
711
     *             if the new component overlaps with any of the components
712
     *             already in the grid.
713
     * @throws OutOfBoundsException
714
     *             if the cells are outside the grid area.
715
     */
716
    public void addComponent(Component component, int column1, int row1,
717
            int column2, int row2)
718
            throws OverlapsException, OutOfBoundsException {
719
        applyDefaultComponentStyles(component);
720
        gridLayout().addComponent(component, column1, row1, column2, row2);
721
    }
722

    
723
    public void setSaveButtonEnabled(boolean enabled){
724
        save.setEnabled(enabled);
725
    }
726

    
727
    public void withDeleteButton(boolean withDelete){
728

    
729
        if(withDelete){
730
            buttonLayout.setExpandRatio(save, 0);
731
            buttonLayout.setExpandRatio(delete, 1);
732
        } else {
733
            buttonLayout.setExpandRatio(save, 1);
734
            buttonLayout.setExpandRatio(delete, 0);
735
        }
736
        delete.setVisible(withDelete);
737
    }
738

    
739
    public boolean addStatusMessage(String message){
740
        boolean returnVal = statusMessages.add(message);
741
        updateStatusLabel();
742
        return returnVal;
743
    }
744

    
745
    public boolean removeStatusMessage(String message){
746
        boolean returnVal = statusMessages.remove(message);
747
        updateStatusLabel();
748
        return returnVal;
749
    }
750

    
751
    /**
752
     *
753
     */
754
    private void updateStatusLabel() {
755
        String text = "";
756
        for(String s : statusMessages){
757
            text += s + " ";
758
        }
759
        statusMessageLabel.setValue(text);
760
        statusMessageLabel.setVisible(!text.isEmpty());
761
        statusMessageLabel.addStyleName(ValoTheme.LABEL_COLORED);
762
    }
763

    
764
    private void updateContextBreadcrumbs() {
765

    
766
        List<EditorActionContext> contextInfo = new ArrayList<>(getEditorActionContext());
767
        String breadcrumbs = "";
768
        EditorActionContextFormatter formatter = new EditorActionContextFormatter();
769

    
770
        GenericFontIcon operationPrefixIcon = new GenericFontIcon("IcoMoon", 0xe902);
771
        GenericFontIcon operationSuffxIcon = new GenericFontIcon("IcoMoon", 0xe901);
772

    
773
        int cnt = 0;
774
        for(EditorActionContext cntxt : contextInfo){
775
            cnt++;
776
            boolean isLast = cnt == contextInfo.size();
777
            boolean isFirst = cnt == 1;
778

    
779
            boolean doClass = false; // will be removed in future
780
            boolean classNameForMissingPropertyPath = true; // !doClass;
781
            boolean doProperties = true;
782
            boolean doCreateOrNew = !isFirst;
783
            String contextmarkup = formatter.format(
784
                    cntxt,
785
                    new EditorActionContextFormat(doClass, doProperties, classNameForMissingPropertyPath, doCreateOrNew,
786
                            EditorActionContextFormat.TargetInfoType.FIELD_CAPTION, (isLast ? "active" : ""))
787
                    );
788
//            if(!isLast){
789
//                contextmarkup += " " + FontAwesome.ANGLE_RIGHT.getHtml() + " ";
790
//            }
791
            if(isLast){
792
                contextmarkup = "<li><span class=\"crumb active\">" + contextmarkup + "</span></li>";
793
            } else {
794
                contextmarkup = "<li><span class=\"crumb\">" + contextmarkup + "</span></li>";
795
            }
796
            breadcrumbs += contextmarkup;
797
        }
798
        contextBreadcrumbsLabel.setValue("<ul class=\"breadcrumbs\">" + breadcrumbs + "</ul>");
799
    }
800

    
801
    // ------------------------ data binding ------------------------ //
802

    
803
    protected void bindDesign(Component component) {
804
        fieldLayout.removeAllComponents();
805
        fieldGroup.bindMemberFields(component);
806
        fieldLayout.addComponent(component);
807
    }
808

    
809

    
810
    public final void loadInEditor(Object identifier) {
811

    
812
        DTO beanToEdit = getPresenter().loadBeanById(identifier);
813
        fieldGroup.setItemDataSource(beanToEdit);
814
        afterItemDataSourceSet();
815
        getPresenter().onViewFormReady(beanToEdit);
816
        updateContextBreadcrumbs();
817
        isBeanLoaded = true;
818
    }
819

    
820
    /**
821
     * Passes the beanInstantiator to the presenter method {@link AbstractEditorPresenter#setBeanInstantiator(BeanInstantiator)}
822
     *
823
     * @param beanInstantiator
824
     */
825
    public final void setBeanInstantiator(BeanInstantiator<DTO> beanInstantiator) {
826
        if(AbstractCdmEditorPresenter.class.isAssignableFrom(getPresenter().getClass())){
827
            ((CdmEditorPresenterBase)getPresenter()).setBeanInstantiator(beanInstantiator);
828
        } else {
829
            throw new RuntimeException("BeanInstantiator can only be set for popup editors with a peresenter of the type CdmEditorPresenterBase");
830
        }
831
    }
832

    
833
    /**
834
     * Returns the bean contained in the itemDatasource of the fieldGroup.
835
     *
836
     * @return
837
     */
838
    public DTO getBean() {
839
        if(fieldGroup.getItemDataSource() != null){
840
            return fieldGroup.getItemDataSource().getBean();
841

    
842
        }
843
        return null;
844
    }
845

    
846
    /**
847
     * @return true once the bean has been loaded indicating that all fields have
848
     *   been setup configured so that the editor is ready for use.
849
     */
850
    public boolean isBeanLoaded() {
851
        return isBeanLoaded;
852
    }
853

    
854
    /**
855
     * This method should only be used by the presenter of this view
856
     *
857
     * @param bean
858
     */
859
    protected void updateItemDataSource(DTO bean) {
860
        fieldGroup.getItemDataSource().setBean(bean);
861
    }
862

    
863
    /**
864
     * This method is called after setting the item data source whereby the
865
     * {@link FieldGroup#configureField(Field<?> field)} method will be called.
866
     * In this method all fields are set to default states defined for the fieldGroup.
867
     * <p>
868
     * You can now implement this method if you need to modify the state or value of individual fields.
869
     */
870
    protected void afterItemDataSourceSet() {
871
        if(editorComponentsConfigurator != null){
872
            editorComponentsConfigurator.updateComponentStates(this);
873
        }
874
    }
875

    
876
    // ------------------------ issue related temporary solutions --------------------- //
877
    /**
878
     * Publicly accessible equivalent to getPreseneter(), needed for
879
     * managing the presenter listeners.
880
     * <p>
881
     * TODO: refactor the presenter listeners management to get rid of this method
882
     *
883
     * @return
884
     * @deprecated marked deprecated to emphasize on the special character of this method
885
     *    which should only be used internally see #6673
886
     */
887
    @Deprecated
888
    public P presenter() {
889
        return getPresenter();
890
    }
891

    
892
    /**
893
     * Returns the context of editor actions for this editor.
894
     * The context submitted with {@link #setParentContext(Stack)} will be updated
895
     * to represent the current context.
896
     *
897
     * @return the context
898
     */
899
    public Stack<EditorActionContext> getEditorActionContext() {
900
        if(!isContextUpdated){
901
            if(getBean() == null){
902
                throw new RuntimeException("getContext() is only possible after the bean is loaded");
903
            }
904
            context.push(new EditorActionContext(getBean(), this));
905
            isContextUpdated = true;
906
        }
907
        return context;
908
    }
909

    
910
    /**
911
     * Set the context of editor actions parent to this editor
912
     *
913
     * @param context the context to set
914
     */
915
    public void setParentEditorActionContext(Stack<EditorActionContext> context, Field<?> targetField) {
916
        if(context != null){
917
            this.context.addAll(context);
918
        }
919
        if(targetField != null){
920
            this.context.get(context.size() - 1).setTargetField(targetField);
921
        }
922
    }
923

    
924
    protected AbstractField<String> replaceComponent(String propertyId, AbstractField<String> oldField, AbstractField<String> newField, int column1, int row1, int column2,
925
            int row2) {
926
                String value = oldField.getValue();
927
                newField.setCaption(oldField.getCaption());
928
                GridLayout grid = (GridLayout)getFieldLayout();
929
                grid.removeComponent(oldField);
930

    
931
                unbindField(oldField);
932
                addField(newField, propertyId, column1, row1, column2, row2);
933
                getViewEventBus().publish(this, new FieldReplaceEvent(this, oldField, newField));
934
                // important: set newField value at last!
935
                newField.setValue(value);
936
                return newField;
937
            }
938

    
939
    public EditorFormConfigurator<? extends AbstractPopupEditor<DTO, P>> getEditorComponentsConfigurator() {
940
        return editorComponentsConfigurator;
941
    }
942

    
943
    public void setEditorComponentsConfigurator(
944
            EditorFormConfigurator<? extends AbstractPopupEditor<DTO, P>> editorComponentsConfigurator) {
945
        this.editorComponentsConfigurator = editorComponentsConfigurator;
946
    }
947

    
948
}
(6-6/15)