Project

General

Profile

Download (9.49 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.component;
10

    
11
import java.util.ArrayList;
12
import java.util.Arrays;
13
import java.util.Collection;
14
import java.util.List;
15
import java.util.Optional;
16

    
17
import com.vaadin.data.fieldgroup.FieldGroup;
18
import com.vaadin.data.fieldgroup.FieldGroup.CommitEvent;
19
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
20
import com.vaadin.data.fieldgroup.FieldGroup.CommitHandler;
21
import com.vaadin.ui.AbstractField;
22
import com.vaadin.ui.Button;
23
import com.vaadin.ui.Component;
24
import com.vaadin.ui.CustomField;
25
import com.vaadin.ui.Field;
26
import com.vaadin.ui.HasComponents;
27

    
28
import eu.etaxonomy.cdm.vaadin.event.NestedButtonStateUpdater;
29

    
30
/**
31
 * TODO implement height methods for full component size support
32
 *
33
 * Implementations need to override {@link  AbstractField#setInternalValue(TeamOrPersonBase<?> newValue)} in order to
34
 * to set the item datasource of the fieldGroup for example:
35
 * <pre>
36
 * @Override
37
   protected void setInternalValue(TeamOrPersonBase<?> newValue) {
38
     ...
39
     fieldGroup.setItemDataSource(new BeanItem<Team>((Team)newValue));
40
     ...
41
   }
42
 * </pre>
43
 *
44
 * @author a.kohlbecker
45
 * @since May 12, 2017
46
 *
47
 * IMPORTANT see also {@link CompositeStyledComponent} which has almost the same functionality.
48
 *
49
 */
50
@SuppressWarnings("serial")
51
public abstract class CompositeCustomField<T> extends CustomField<T> implements NestedFieldGroup {
52

    
53
    protected static final String READ_ONLY_CAPTION_SUFFIX = " (read only)";
54

    
55
    private List<Component> styledComponents = new ArrayList<>();
56

    
57
    private List<Component> sizedComponents = new ArrayList<>();
58

    
59
    private CommitHandler commitHandler = new CommitHandler() {
60

    
61
        @Override
62
        public void preCommit(CommitEvent commitEvent) throws CommitException {
63
            // commit the nested bean(s) first
64
            if(getFieldGroup().isPresent()){
65
                getFieldGroup().get().commit();
66
            }
67
        }
68

    
69
        @Override
70
        public void postCommit(CommitEvent commitEvent) throws CommitException {
71
            // noting to do
72
        }};
73

    
74
    protected List<Component> getStyledComponents() {
75
        if(styledComponents == null){
76
            styledComponents = new ArrayList<>();
77
        }
78
        return styledComponents;
79
    }
80

    
81
    /**
82
     * Implementations preferably call this method in the constructor
83
     *
84
     * @param component
85
     * @return
86
     */
87
    protected boolean addStyledComponent(Component component){
88
        applyCurrentStyleNames(component);
89
        return styledComponents.add(component);
90
    }
91

    
92
    /**
93
     * Implementations preferably call this method in the constructor
94
     *
95
     * @param component
96
     * @return
97
     */
98
    protected boolean addStyledComponents(Component ... component){
99
        List<Component> componentList = Arrays.asList(component);
100
        componentList.forEach(c -> applyCurrentStyleNames(c));
101
        return styledComponents.addAll(componentList);
102
    }
103

    
104
    protected List<Component> getSizedComponents() {
105
        if(sizedComponents == null){
106
            sizedComponents = new ArrayList<>();
107
        }
108
        return sizedComponents;
109
    }
110

    
111
    /**
112
     * Implementations preferably call this method in the constructor
113
     *
114
     * @param component
115
     * @return
116
     */
117
    protected boolean addSizedComponent(Component component){
118
        applyCurrentSize(component);
119
        return sizedComponents.add(component);
120
    }
121

    
122
    /**
123
     * Implementations preferably call this method in the constructor
124
     *
125
     * @param component
126
     * @return
127
     */
128
    protected boolean addSizedComponents(Component ... component){
129
        List<Component> componentList = Arrays.asList(component);
130
        componentList.forEach(c -> applyCurrentSize(c));
131
        return sizedComponents.addAll(componentList);
132
    }
133

    
134
    @Override
135
    public void setWidth(String width) {
136
        super.setWidth(width);
137
        getSizedComponents().forEach(c -> {if(c != null) {c.setWidth(width);}});
138
    }
139

    
140
    @Override
141
    public void setWidth(float width, Unit unit){
142
        super.setWidth(width, unit);
143
        getSizedComponents().forEach(c -> {if(c != null) {c.setWidth(width, unit);}});
144
    }
145

    
146
    @Override
147
    public void setWidthUndefined() {
148
        super.setWidthUndefined();
149
        getSizedComponents().forEach(c -> {if(c != null) {c.setWidthUndefined();}});
150
    }
151

    
152
    @Override
153
    public void setStyleName(String style) {
154
        super.setStyleName(style);
155
        getStyledComponents().forEach(c -> c.setStyleName(style));
156
        addDefaultStyles();
157
    }
158

    
159
    @Override
160
    public void addStyleName(String style) {
161
        super.addStyleName(style);
162
        getStyledComponents().forEach(c -> c.addStyleName(style));
163
    }
164

    
165
    protected void applyCurrentStyleNames(Component newSubComponent){
166
        newSubComponent.setStyleName(getStyleName());
167
    }
168

    
169
    protected void applyCurrentSize(Component newSubComponent){
170
        newSubComponent.setWidth(this.getWidth(), this.getWidthUnits());
171
    }
172

    
173
    /**
174
     * Implementations can may apply default styles to components added to <code>StyledComponents</code>
175
     * to prevent these styles from being overwritten when setStyleName() id called on the composite field.
176
     */
177
    protected abstract void addDefaultStyles();
178

    
179
    /**
180
     * Implementations return the local fieldGroup
181
     *
182
     * @return
183
     */
184
    @Override
185
    public abstract Optional<FieldGroup> getFieldGroup();
186

    
187
    /**
188
     * @return true if all fields having the value <code>null</code> and if there is no fieldgroup at all for this component.
189
     */
190
    @SuppressWarnings("rawtypes")
191
    public boolean hasNullContent() {
192
        Collection<Field> nullValueCheckIgnore = nullValueCheckIgnoreFields();
193
        if(!getFieldGroup().isPresent()){
194
            return true;
195
        }
196
        return getFieldGroup().get().getFields().stream()
197
                .filter(
198
                        f -> !nullValueCheckIgnore.contains(f)
199
                )
200
                //.peek( f -> System.out.println("###> " + f.getCaption() + ": " + f.getValue()))
201
                .allMatch(
202
                        f -> {
203
                            if(f instanceof CompositeCustomField){
204
                                return ((CompositeCustomField)f).hasNullContent();
205
                            } else {
206
                                if(f.getValue() == null) {
207
                                    return true;
208
                                } else {
209
                                    return false;
210
                                }
211
                            }
212
                        }
213
                );
214
    }
215

    
216
    /**
217
     * @return
218
     */
219
    protected List<Field> nullValueCheckIgnoreFields() {
220
        // TODO Auto-generated method stub
221
        return new ArrayList<Field>(0);
222
    }
223

    
224
    @Override
225
    public void registerParentFieldGroup(FieldGroup parent) {
226
        parent.addCommitHandler(commitHandler);
227
    }
228

    
229
    @Override
230
    public void unregisterParentFieldGroup(FieldGroup parent) {
231
        parent.removeCommitHandler(commitHandler);
232
    }
233

    
234
    /**
235
     *
236
     * @param readOnly
237
     *            the readonly state
238
     * @param component
239
     *            the component to process on
240
     * @param ignore
241
     *            can be <code>null</code>
242
     */
243
    protected void setDeepReadOnly(boolean readOnly, Component component, Collection<Component> ignore) {
244

    
245
        if(ignore != null && ignore.contains(component)){
246
            return;
247
        }
248

    
249
        applyReadOnlyState(component, readOnly);
250
        if(HasComponents.class.isAssignableFrom(component.getClass())){
251
            for(Component nestedComponent : ((HasComponents)component)){
252
                setDeepReadOnly(readOnly, nestedComponent, ignore);
253
            }
254
        }
255
    }
256

    
257
    /**
258
     * Sets the readonly state for the component but treats Buttons differently.
259
     * For nested Buttons the readonly state is projected to enabled state to
260
     * make them inactive. Finally NestedButtonStateUpdaters are triggered to
261
     * allow to disable buttons accordingly to user permissions.
262
     *
263
     * @param readOnly
264
     * @param component
265
     */
266
    protected void applyReadOnlyState(Component component, boolean readOnly) {
267
        component.setReadOnly(readOnly);
268
        if(Button.class.isAssignableFrom(component.getClass())){
269
            component.setEnabled(!readOnly);
270
        }
271
        triggerNestedButtonStateUpdaters();
272
    }
273

    
274

    
275

    
276
    /**
277
     *
278
     */
279
    protected void triggerNestedButtonStateUpdaters() {
280
        for(Object l : getListeners(AbstractField.ValueChangeEvent.class)){
281
           if(NestedButtonStateUpdater.class.isAssignableFrom(l.getClass())){
282
               // trigger a fake ValueChangeEvent to let the ToOneRelatedEntityButtonUpdater fix the states
283
               // of nested buttons
284
               ((NestedButtonStateUpdater)l).valueChange(new AbstractField.ValueChangeEvent(this));
285
           }
286
        }
287

    
288
    }
289

    
290
    @Override
291
    public String toString(){
292
        return this.getClass().getSimpleName() + ": " +
293
                ( getValue() != null ? getValue() : "null");
294
    }
295

    
296
    protected void updateCaptionReadonlyNotice(boolean readOnly) {
297
        if(readOnly){
298
            if(!getCaption().contains(READ_ONLY_CAPTION_SUFFIX)){
299
                setCaption(getCaption() + READ_ONLY_CAPTION_SUFFIX);
300
            }
301
        } else {
302
            setCaption(getCaption().replace(READ_ONLY_CAPTION_SUFFIX, ""));
303
        }
304
    }
305

    
306
}
(1-1/19)