Project

General

Profile

Download (34 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
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
135

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

    
142
        setCompositionRoot(mainLayout);
143

    
144
        fieldGroup = new BeanFieldGroup<>(dtoType);
145
        fieldGroup.addCommitHandler(new SaveHandler());
146

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

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

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

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

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

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

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

    
191
        statusMessageLabel.setWidthUndefined();
192

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

    
197
        updateToolBarVisibility();
198
    }
199

    
200
    protected VerticalLayout getMainLayout() {
201
        return mainLayout;
202
    }
203

    
204
    protected Layout getFieldLayout() {
205
        return fieldLayout;
206
    }
207

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

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

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

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

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

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

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

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

    
298
    }
299

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

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

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

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

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

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

    
345

    
346
    // ------------------------ event handler ------------------------ //
347

    
348
    private class SaveHandler implements CommitHandler {
349

    
350
        private static final long serialVersionUID = 2047223089707080659L;
351

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

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

    
379
    protected void addCommitHandler(CommitHandler commitHandler) {
380
        fieldGroup.addCommitHandler(commitHandler);
381
    }
382

    
383
    protected void cancelEditorDialog(){
384

    
385
        if(fieldGroup.isModified()){
386

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

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

    
405

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

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

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

    
451
        }
452
    }
453

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

    
465
    }
466

    
467
    // ------------------------ field adding methods ------------------------ //
468

    
469

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
581
    public PropertyIdPath boundPropertyIdPath(Field<?> field){
582

    
583
        PropertyIdPath propertyIdPath = null;
584
        Object propertyId = fieldGroup.getPropertyId(field);
585

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

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

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

    
641
    protected void unbindField(Field field){
642
        fieldGroup.unbind(field);
643
    }
644

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

    
652
    protected abstract String getDefaultComponentStyles();
653

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

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

    
721
    public void setSaveButtonEnabled(boolean enabled){
722
        save.setEnabled(enabled);
723
    }
724

    
725
    public void withDeleteButton(boolean withDelete){
726

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

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

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

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

    
762
    private void updateContextBreadcrumbs() {
763

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

    
768
        GenericFontIcon operationPrefixIcon = new GenericFontIcon("IcoMoon", 0xe902);
769
        GenericFontIcon operationSuffxIcon = new GenericFontIcon("IcoMoon", 0xe901);
770

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

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

    
799
    // ------------------------ data binding ------------------------ //
800

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

    
807

    
808
    public final void loadInEditor(Object identifier) {
809

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

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

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

    
840
        }
841
        return null;
842
    }
843

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

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

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

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

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

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

    
919
    protected AbstractField<String> replaceComponent(String propertyId, AbstractField<String> oldField, AbstractField<String> newField, int column1, int row1, int column2,
920
            int row2) {
921
                String value = oldField.getValue();
922
                newField.setCaption(oldField.getCaption());
923
                GridLayout grid = (GridLayout)getFieldLayout();
924
                grid.removeComponent(oldField);
925

    
926
                unbindField(oldField);
927
                addField(newField, propertyId, column1, row1, column2, row2);
928
                getViewEventBus().publish(this, new FieldReplaceEvent(this, oldField, newField));
929
                // important: set newField value at last!
930
                newField.setValue(value);
931
                return newField;
932
            }
933

    
934
}
(6-6/14)