Project

General

Profile

Download (32.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.CheckBox;
42
import com.vaadin.ui.Component;
43
import com.vaadin.ui.CssLayout;
44
import com.vaadin.ui.Field;
45
import com.vaadin.ui.GridLayout;
46
import com.vaadin.ui.GridLayout.OutOfBoundsException;
47
import com.vaadin.ui.GridLayout.OverlapsException;
48
import com.vaadin.ui.HasComponents;
49
import com.vaadin.ui.HorizontalLayout;
50
import com.vaadin.ui.Label;
51
import com.vaadin.ui.Layout;
52
import com.vaadin.ui.Layout.MarginHandler;
53
import com.vaadin.ui.Notification;
54
import com.vaadin.ui.Notification.Type;
55
import com.vaadin.ui.PopupDateField;
56
import com.vaadin.ui.TextField;
57
import com.vaadin.ui.VerticalLayout;
58
import com.vaadin.ui.themes.ValoTheme;
59

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

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

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

    
91
    public static final Logger logger = Logger.getLogger(AbstractPopupEditor.class);
92

    
93
    private BeanFieldGroup<DTO> fieldGroup;
94

    
95
    private VerticalLayout mainLayout;
96

    
97
    private Layout fieldLayout;
98

    
99
    private HorizontalLayout buttonLayout;
100

    
101
    private Button save;
102

    
103
    private Button cancel;
104

    
105
    private Button delete;
106

    
107
    private CssLayout toolBar = new CssLayout();
108

    
109
    private CssLayout toolBarButtonGroup = new CssLayout();
110

    
111
    private Label contextBreadcrumbsLabel = new Label();
112

    
113
    private Label statusMessageLabel = new Label();
114

    
115
    Set<String> statusMessages = new HashSet<>();
116

    
117
    private GridLayout _gridLayoutCache;
118

    
119
    private boolean isBeanLoaded;
120

    
121
    private Stack<EditorActionContext> context = new Stack<EditorActionContext>();
122

    
123
    private boolean isContextUpdated;
124

    
125
    private boolean isAdvancedMode = false;
126

    
127
    private List<Component> advancedModeComponents = new ArrayList<>();
128

    
129
    private Button advancedModeButton;
130

    
131
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
132

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

    
139
        setCompositionRoot(mainLayout);
140

    
141
        fieldGroup = new BeanFieldGroup<>(dtoType);
142
        fieldGroup.addCommitHandler(new SaveHandler());
143

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

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

    
164
        buttonLayout = new HorizontalLayout();
165
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
166
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
167
        buttonLayout.setSpacing(true);
168

    
169
        save = new Button("Save", FontAwesome.SAVE);
170
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
171
        save.addClickListener(e -> save());
172

    
173
        cancel = new Button("Cancel", FontAwesome.REMOVE);
174
        cancel.addClickListener(e -> cancel());
175

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

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

    
188
        statusMessageLabel.setWidthUndefined();
189

    
190
        mainLayout.addComponents(toolBar, fieldLayout, statusMessageLabel, buttonLayout);
191
        mainLayout.setComponentAlignment(statusMessageLabel, Alignment.BOTTOM_RIGHT);
192
        mainLayout.setComponentAlignment(toolBar, Alignment.TOP_RIGHT);
193

    
194
        updateToolBarVisibility();
195
    }
196

    
197
    protected VerticalLayout getMainLayout() {
198
        return mainLayout;
199
    }
200

    
201
    protected Layout getFieldLayout() {
202
        return fieldLayout;
203
    }
204

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

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

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

    
248
    /**
249
     * @return
250
     * @return
251
     */
252
    protected AbstractLayout getToolBar() {
253
        return toolBar;
254
    }
255

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

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

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

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

    
295
    }
296

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

    
306
    /**
307
     * @return the isAdvancedMode
308
     */
309
    public boolean isAdvancedMode() {
310
        return isAdvancedMode;
311
    }
312

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

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

    
332
        } else if(advancedModeButton != null) {
333
            toolBarButtonGroupRemove(advancedModeButton);
334
            advancedModeButton = null;
335
        }
336
    }
337

    
338
    public void registerAdvancedModeComponents(Component ... c){
339
        advancedModeComponents.addAll(Arrays.asList(c));
340
    }
341

    
342

    
343
    // ------------------------ event handler ------------------------ //
344

    
345
    private class SaveHandler implements CommitHandler {
346

    
347
        private static final long serialVersionUID = 2047223089707080659L;
348

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

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

    
376
    protected void addCommitHandler(CommitHandler commitHandler) {
377
        fieldGroup.addCommitHandler(commitHandler);
378
    }
379

    
380

    
381
    /**
382
     * Cancel editing and discard all modifications.
383
     */
384
    @Override
385
    public void cancel() {
386
        fieldGroup.discard();
387
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.CANCEL));
388
    }
389

    
390
    /**
391
     * @return
392
     */
393
    private void delete() {
394
        viewEventBus.publish(this, new EditorDeleteEvent<DTO>(this, fieldGroup.getItemDataSource().getBean()));
395
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.DELETE));
396
    }
397

    
398
    /**
399
     * Save the changes made in the editor.
400
     */
401
    private void save() {
402
        try {
403
            fieldGroup.commit();
404
        } catch (CommitException e) {
405
            fieldGroup.getFields().forEach(f -> ((AbstractField<?>)f).setValidationVisible(true));
406
            if(e.getCause() != null && e.getCause() instanceof FieldGroupInvalidValueException){
407
                FieldGroupInvalidValueException invalidValueException = (FieldGroupInvalidValueException)e.getCause();
408
                updateFieldNotifications(invalidValueException.getInvalidFields());
409
                Notification.show("The entered data in " + invalidValueException.getInvalidFields().size() + " fields is incomplete or invalid.");
410
            } else if(e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof PermissionDeniedException){
411
                PermissionDeniedException permissionDeniedException = (PermissionDeniedException)e.getCause().getCause();
412
                Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
413
            } else {
414
                throw new PopupEditorException("Error saving popup editor", this, e);
415
            }
416
        }
417
    }
418

    
419
    /**
420
     * @param invalidFields
421
     */
422
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
423
        for(Field<?> f : invalidFields.keySet()){
424
            if(f instanceof AbstractField){
425
                String message = invalidFields.get(f).getHtmlMessage();
426
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
427
            }
428
        }
429

    
430
    }
431

    
432
    // ------------------------ field adding methods ------------------------ //
433

    
434

    
435
    protected TextField addTextField(String caption, String propertyId) {
436
        return addField(new TextFieldNFix(caption), propertyId);
437
    }
438

    
439
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
440
            int column2, int row2)
441
            throws OverlapsException, OutOfBoundsException {
442
        return addField(new TextFieldNFix(caption), propertyId, column1, row1, column2, row2);
443
    }
444

    
445
    protected TextField addTextField(String caption, String propertyId, int column, int row)
446
            throws OverlapsException, OutOfBoundsException {
447
        return addField(new TextFieldNFix(caption), propertyId, column, row);
448
    }
449

    
450
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
451
            int column2, int row2)
452
            throws OverlapsException, OutOfBoundsException {
453

    
454
        SwitchableTextField field = new SwitchableTextField(caption);
455
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
456
        addComponent(field, column1, row1, column2, row2);
457
        return field;
458
    }
459

    
460
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
461
            throws OverlapsException, OutOfBoundsException {
462

    
463
        SwitchableTextField field = new SwitchableTextField(caption);
464
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
465
        addComponent(field, column, row);
466
        return field;
467
    }
468

    
469
    protected PopupDateField addDateField(String caption, String propertyId) {
470
        return addField(new PopupDateField(caption), propertyId);
471
    }
472

    
473
    protected CheckBox addCheckBox(String caption, String propertyId) {
474
        return addField(new CheckBox(caption), propertyId);
475
    }
476

    
477
    protected CheckBox addCheckBox(String caption, String propertyId, int column, int row){
478
        return addField(new CheckBox(caption), propertyId, column, row);
479
    }
480

    
481
    protected <T extends Field> T addField(T field, String propertyId) {
482
        fieldGroup.bind(field, propertyId);
483
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
484
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
485
        }
486
        addComponent(field);
487
        return field;
488
    }
489

    
490
    /**
491
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
492
     *
493
     * @param field
494
     *            the field to be added, not <code>null</code>.
495
     * @param propertyId
496
     * @param column
497
     *            the column index, starting from 0.
498
     * @param row
499
     *            the row index, starting from 0.
500
     * @throws OverlapsException
501
     *             if the new component overlaps with any of the components
502
     *             already in the grid.
503
     * @throws OutOfBoundsException
504
     *             if the cell is outside the grid area.
505
     */
506
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
507
            throws OverlapsException, OutOfBoundsException {
508
        fieldGroup.bind(field, propertyId);
509
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
510
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
511
        }
512
        addComponent(field, column, row);
513
        return field;
514
    }
515

    
516
    /**
517
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
518
     *
519
     * @param field
520
     * @param propertyId
521
     * @param column1
522
     * @param row1
523
     * @param column2
524
     * @param row2
525
     * @return
526
     * @throws OverlapsException
527
     * @throws OutOfBoundsException
528
     */
529
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
530
            int column2, int row2)
531
            throws OverlapsException, OutOfBoundsException {
532
        if(propertyId != null){
533
            fieldGroup.bind(field, propertyId);
534
            if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
535
                ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
536
            }
537
        }
538
        addComponent(field, column1, row1, column2, row2);
539
        return field;
540
    }
541

    
542
    protected Field<?> getField(Object propertyId){
543
        return fieldGroup.getField(propertyId);
544
    }
545

    
546
    public PropertyIdPath boundPropertyIdPath(Field<?> field){
547

    
548
        PropertyIdPath propertyIdPath = null;
549
        Object propertyId = fieldGroup.getPropertyId(field);
550

    
551
        if(propertyId == null){
552
            // not found in the editor field group. Maybe the field is bound to a nested fieldgroup?
553
            // 1. find the NestedFieldGroup implementations from the field up to the editor
554
            logger.setLevel(Level.DEBUG);
555
            PropertyIdPath nestedPropertyIds = new PropertyIdPath();
556
            Field parentField = field;
557
            HasComponents parentComponent = parentField.getParent();
558
            logger.debug("field: " + parentField.getClass().getSimpleName());
559
            while(parentComponent != null){
560
                logger.debug("parentComponent: " + parentComponent.getClass().getSimpleName());
561
                if(NestedFieldGroup.class.isAssignableFrom(parentComponent.getClass()) && AbstractField.class.isAssignableFrom(parentComponent.getClass())){
562
                    Object propId = ((NestedFieldGroup)parentComponent).getFieldGroup().getPropertyId(parentField);
563
                    if(propId != null){
564
                        logger.debug("propId: " + propId.toString());
565
                        nestedPropertyIds.addParent(propId);
566
                    }
567
                    logger.debug("parentField: " + parentField.getClass().getSimpleName());
568
                    parentField = (Field)parentComponent;
569
                } else if(parentComponent == this) {
570
                    // we reached the editor itself
571
                    Object propId = fieldGroup.getPropertyId(parentField);
572
                    if(propId != null){
573
                        logger.debug("propId: " + propId.toString());
574
                        nestedPropertyIds.addParent(propId);
575
                    }
576
                    propertyIdPath = nestedPropertyIds;
577
                    break;
578
                }
579
                parentComponent = parentComponent.getParent();
580
            }
581
            // 2. check the NestedFieldGroup binding the field is direct or indirect child component of the editor
582
//            NO lONGER NEEDED
583
//            parentComponent = parentField.getParent(); // get component containing the last parent field found
584
//            while(true){
585
//                if(parentComponent == getFieldLayout()){
586
//                    propertyIdPath = nestedPropertyIds;
587
//                    break;
588
//                }
589
//                parentComponent = parentComponent.getParent();
590
//            }
591
        } else {
592
            propertyIdPath = new PropertyIdPath(propertyId);
593
        }
594
        return propertyIdPath;
595
    }
596

    
597
    protected void addComponent(Component component) {
598
        fieldLayout.addComponent(component);
599
        applyDefaultComponentStyles(component);
600
    }
601

    
602
    protected void bindField(Field field, String propertyId){
603
        fieldGroup.bind(field, propertyId);
604
    }
605

    
606
    protected void unbindField(Field field){
607
        fieldGroup.unbind(field);
608
    }
609

    
610
    /**
611
     * @param component
612
     */
613
    public void applyDefaultComponentStyles(Component component) {
614
        component.addStyleName(getDefaultComponentStyles());
615
    }
616

    
617
    protected abstract String getDefaultComponentStyles();
618

    
619
    /**
620
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
621
     * <p>
622
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
623
     * the area.) End coordinates (SouthEast corner of the area) are the same as
624
     * column1,row1. The coordinates are zero-based. Component width and height
625
     * is 1.
626
     *
627
     * @param component
628
     *            the component to be added, not <code>null</code>.
629
     * @param column
630
     *            the column index, starting from 0.
631
     * @param row
632
     *            the row index, starting from 0.
633
     * @throws OverlapsException
634
     *             if the new component overlaps with any of the components
635
     *             already in the grid.
636
     * @throws OutOfBoundsException
637
     *             if the cell is outside the grid area.
638
     */
639
    public void addComponent(Component component, int column, int row)
640
            throws OverlapsException, OutOfBoundsException {
641
        applyDefaultComponentStyles(component);
642
        gridLayout().addComponent(component, column, row, column, row);
643
    }
644

    
645
    /**
646
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
647
     * <p>
648
     * Adds a component to the grid in the specified area. The area is defined
649
     * by specifying the upper left corner (column1, row1) and the lower right
650
     * corner (column2, row2) of the area. The coordinates are zero-based.
651
     * </p>
652
     *
653
     * <p>
654
     * If the area overlaps with any of the existing components already present
655
     * in the grid, the operation will fail and an {@link OverlapsException} is
656
     * thrown.
657
     * </p>
658
     *
659
     * @param component
660
     *            the component to be added, not <code>null</code>.
661
     * @param column1
662
     *            the column of the upper left corner of the area <code>c</code>
663
     *            is supposed to occupy. The leftmost column has index 0.
664
     * @param row1
665
     *            the row of the upper left corner of the area <code>c</code> is
666
     *            supposed to occupy. The topmost row has index 0.
667
     * @param column2
668
     *            the column of the lower right corner of the area
669
     *            <code>c</code> is supposed to occupy.
670
     * @param row2
671
     *            the row of the lower right corner of the area <code>c</code>
672
     *            is supposed to occupy.
673
     * @throws OverlapsException
674
     *             if the new component overlaps with any of the components
675
     *             already in the grid.
676
     * @throws OutOfBoundsException
677
     *             if the cells are outside the grid area.
678
     */
679
    public void addComponent(Component component, int column1, int row1,
680
            int column2, int row2)
681
            throws OverlapsException, OutOfBoundsException {
682
        applyDefaultComponentStyles(component);
683
        gridLayout().addComponent(component, column1, row1, column2, row2);
684
    }
685

    
686
    public void setSaveButtonEnabled(boolean enabled){
687
        save.setEnabled(enabled);
688
    }
689

    
690
    public void withDeleteButton(boolean withDelete){
691

    
692
        if(withDelete){
693
            buttonLayout.setExpandRatio(save, 0);
694
            buttonLayout.setExpandRatio(delete, 1);
695
        } else {
696
            buttonLayout.setExpandRatio(save, 1);
697
            buttonLayout.setExpandRatio(delete, 0);
698
        }
699
        delete.setVisible(withDelete);
700
    }
701

    
702
    public boolean addStatusMessage(String message){
703
        boolean returnVal = statusMessages.add(message);
704
        updateStatusLabel();
705
        return returnVal;
706
    }
707

    
708
    public boolean removeStatusMessage(String message){
709
        boolean returnVal = statusMessages.remove(message);
710
        updateStatusLabel();
711
        return returnVal;
712
    }
713

    
714
    /**
715
     *
716
     */
717
    private void updateStatusLabel() {
718
        String text = "";
719
        for(String s : statusMessages){
720
            text += s + " ";
721
        }
722
        statusMessageLabel.setValue(text);
723
        statusMessageLabel.setVisible(!text.isEmpty());
724
        statusMessageLabel.addStyleName(ValoTheme.LABEL_COLORED);
725
    }
726

    
727
    private void updateContextBreadcrumbs() {
728

    
729
        List<EditorActionContext> contextInfo = new ArrayList<>(getEditorActionContext());
730
        String breadcrumbs = "";
731
        EditorActionContextFormatter formatter = new EditorActionContextFormatter();
732

    
733
        GenericFontIcon operationPrefixIcon = new GenericFontIcon("IcoMoon", 0xe902);
734
        GenericFontIcon operationSuffxIcon = new GenericFontIcon("IcoMoon", 0xe901);
735

    
736
        int cnt = 0;
737
        for(EditorActionContext cntxt : contextInfo){
738
            cnt++;
739
            boolean isLast = cnt == contextInfo.size();
740
            boolean isFirst = cnt == 1;
741

    
742
            boolean doClass = false; // will be removed in future
743
            boolean classNameForMissingPropertyPath = true; // !doClass;
744
            boolean doProperties = true;
745
            boolean doCreateOrNew = !isFirst;
746
            String contextmarkup = formatter.format(
747
                    cntxt,
748
                    new EditorActionContextFormat(doClass, doProperties, classNameForMissingPropertyPath, doCreateOrNew,
749
                            EditorActionContextFormat.TargetInfoType.FIELD_CAPTION, (isLast ? "active" : ""))
750
                    );
751
//            if(!isLast){
752
//                contextmarkup += " " + FontAwesome.ANGLE_RIGHT.getHtml() + " ";
753
//            }
754
            if(isLast){
755
                contextmarkup = "<li><span class=\"crumb active\">" + contextmarkup + "</span></li>";
756
            } else {
757
                contextmarkup = "<li><span class=\"crumb\">" + contextmarkup + "</span></li>";
758
            }
759
            breadcrumbs += contextmarkup;
760
        }
761
        contextBreadcrumbsLabel.setValue("<ul class=\"breadcrumbs\">" + breadcrumbs + "</ul>");
762
    }
763

    
764
    // ------------------------ data binding ------------------------ //
765

    
766
    protected void bindDesign(Component component) {
767
        fieldLayout.removeAllComponents();
768
        fieldGroup.bindMemberFields(component);
769
        fieldLayout.addComponent(component);
770
    }
771

    
772

    
773
    public final void loadInEditor(Object identifier) {
774

    
775
        DTO beanToEdit = getPresenter().loadBeanById(identifier);
776
        fieldGroup.setItemDataSource(beanToEdit);
777
        afterItemDataSourceSet();
778
        getPresenter().adaptToUserPermission(beanToEdit);
779
        updateContextBreadcrumbs();
780
        isBeanLoaded = true;
781
    }
782

    
783
    /**
784
     * Passes the beanInstantiator to the presenter method {@link AbstractEditorPresenter#setBeanInstantiator(BeanInstantiator)}
785
     *
786
     * @param beanInstantiator
787
     */
788
    public final void setBeanInstantiator(BeanInstantiator<DTO> beanInstantiator) {
789
        if(AbstractCdmEditorPresenter.class.isAssignableFrom(getPresenter().getClass())){
790
            ((CdmEditorPresenterBase)getPresenter()).setBeanInstantiator(beanInstantiator);
791
        } else {
792
            throw new RuntimeException("BeanInstantiator can only be set for popup editors with a peresenter of the type CdmEditorPresenterBase");
793
        }
794
    }
795

    
796
    /**
797
     * Returns the bean contained in the itemDatasource of the fieldGroup.
798
     *
799
     * @return
800
     */
801
    public DTO getBean() {
802
        if(fieldGroup.getItemDataSource() != null){
803
            return fieldGroup.getItemDataSource().getBean();
804

    
805
        }
806
        return null;
807
    }
808

    
809
    /**
810
     * @return true once the bean has been loaded indicating that all fields have
811
     *   been setup configured so that the editor is ready for use.
812
     */
813
    public boolean isBeanLoaded() {
814
        return isBeanLoaded;
815
    }
816

    
817
    /**
818
     * This method should only be used by the presenter of this view
819
     *
820
     * @param bean
821
     */
822
    protected void updateItemDataSource(DTO bean) {
823
        fieldGroup.getItemDataSource().setBean(bean);
824
    }
825

    
826
    /**
827
     * This method is called after setting the item data source whereby the
828
     * {@link FieldGroup#configureField(Field<?> field)} method will be called.
829
     * In this method all fields are set to default states defined for the fieldGroup.
830
     * <p>
831
     * You can now implement this method if you need to modify the state or value of individual fields.
832
     */
833
    protected void afterItemDataSourceSet() {
834
    }
835

    
836
    // ------------------------ issue related temporary solutions --------------------- //
837
    /**
838
     * Publicly accessible equivalent to getPreseneter(), needed for
839
     * managing the presenter listeners.
840
     * <p>
841
     * TODO: refactor the presenter listeners management to get rid of this method
842
     *
843
     * @return
844
     * @deprecated marked deprecated to emphasize on the special character of this method
845
     *    which should only be used internally see #6673
846
     */
847
    @Deprecated
848
    public P presenter() {
849
        return getPresenter();
850
    }
851

    
852
    /**
853
     * Returns the context of editor actions for this editor.
854
     * The context submitted with {@link #setParentContext(Stack)} will be updated
855
     * to represent the current context.
856
     *
857
     * @return the context
858
     */
859
    public Stack<EditorActionContext> getEditorActionContext() {
860
        if(!isContextUpdated){
861
            if(getBean() == null){
862
                throw new RuntimeException("getContext() is only possible after the bean is loaded");
863
            }
864
            context.push(new EditorActionContext(getBean(), this));
865
            isContextUpdated = true;
866
        }
867
        return context;
868
    }
869

    
870
    /**
871
     * Set the context of editor actions parent to this editor
872
     *
873
     * @param context the context to set
874
     */
875
    public void setParentEditorActionContext(Stack<EditorActionContext> context, Field<?> targetField) {
876
        if(context != null){
877
            this.context.addAll(context);
878
        }
879
        if(targetField != null){
880
            this.context.get(context.size() - 1).setTargetField(targetField);
881
        }
882
    }
883

    
884
    protected AbstractField<String> replaceComponent(String propertyId, AbstractField<String> oldField, AbstractField<String> newField, int column1, int row1, int column2,
885
            int row2) {
886
                String value = oldField.getValue();
887
                newField.setCaption(oldField.getCaption());
888
                GridLayout grid = (GridLayout)getFieldLayout();
889
                grid.removeComponent(oldField);
890

    
891
                unbindField(oldField);
892
                addField(newField, propertyId, column1, row1, column2, row2);
893
                getViewEventBus().publish(this, new FieldReplaceEvent(this, oldField, newField));
894
                // important: set newField value at last!
895
                newField.setValue(value);
896
                return newField;
897
            }
898

    
899
}
(6-6/14)