aboutsummaryrefslogtreecommitdiffstats
path: root/server/src/main/java/com/vaadin/ui/AbstractField.java
blob: c87a72e4304e45ebdeefd48987d488dcb40794c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/*
 * Copyright 2000-2016 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.vaadin.ui;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;

import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;

import com.vaadin.data.HasRequired;
import com.vaadin.data.HasValue;
import com.vaadin.shared.AbstractFieldState;
import com.vaadin.shared.Registration;
import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.util.ReflectTools;

/**
 * An abstract implementation of a field, or a {@code Component} allowing user
 * input. Implements {@link HasValue} to represent the input value. Examples of
 * typical field components include text fields, date pickers, and check boxes.
 * <p>
 * This class replaces the Vaadin 7 {@code com.vaadin.ui.AbstractField} class.
 * The old {@code AbstractField} is retained, under the new name
 * {@link com.vaadin.v7.ui.AbstractField}, for compatibility and migration
 * purposes.
 *
 * @author Vaadin Ltd.
 * @since 8.0
 *
 * @param <T>
 *            the input value type
 */
public abstract class AbstractField<T> extends AbstractComponent
        implements HasValue<T>, HasRequired, Focusable {

    @Deprecated
    private static final Method VALUE_CHANGE_METHOD = ReflectTools.findMethod(
            ValueChangeListener.class, "accept", ValueChangeEvent.class);

    @Override
    public void setValue(T value) {
        setValue(value, false);
    }

    /**
     * Returns whether the value of this field can be changed by the user or
     * not. By default fields are not read-only.
     *
     * @return {@code true} if this field is in read-only mode, {@code false}
     *         otherwise.
     *
     * @see #setReadOnly(boolean)
     */
    @Override
    public boolean isReadOnly() {
        return super.isReadOnly();
    }

    /**
     * Sets whether the value of this field can be changed by the user or not. A
     * field in read-only mode typically looks visually different to signal to
     * the user that the value cannot be edited.
     * <p>
     * The server ignores (potentially forged) value change requests from the
     * client to fields that are read-only. Programmatically changing the field
     * value via {@link #setValue(T)} is still possible.
     * <p>
     * The read-only mode is distinct from the
     * {@linkplain Component#setEnabled(boolean) disabled} state. When disabled,
     * a component cannot be interacted with at all, and its content should be
     * considered irrelevant or not applicable. In contrast, the user should
     * still be able to read the content and otherwise interact with a read-only
     * field even though changing the value is disallowed.
     *
     * @param readOnly
     *            {@code true} to set read-only mode, {@code false} otherwise.
     */
    @Override
    public void setReadOnly(boolean readOnly) {
        super.setReadOnly(readOnly);
    }

    @Override
    public Registration addValueChangeListener(
            ValueChangeListener<T> listener) {
        addListener(ValueChangeEvent.class, listener, VALUE_CHANGE_METHOD);
        return () -> removeListener(ValueChangeEvent.class, listener);
    }

    @Override
    public void readDesign(Element design, DesignContext designContext) {
        super.readDesign(design, designContext);
        Attributes attr = design.attributes();
        if (attr.hasKey("readonly")) {
            setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr,
                    Boolean.class));
        }
    }

    @Override
    public void writeDesign(Element design, DesignContext designContext) {
        super.writeDesign(design, designContext);
        AbstractField<T> def = designContext.getDefaultInstance(this);
        Attributes attr = design.attributes();
        DesignAttributeHandler.writeAttribute("readonly", attr,
                super.isReadOnly(), def.isReadOnly(), Boolean.class);
    }

    @Override
    protected Collection<String> getCustomAttributes() {
        Collection<String> attributes = super.getCustomAttributes();
        attributes.add("readonly");
        // must be handled by subclasses
        attributes.add("value");
        return attributes;
    }

    /**
     * Sets the value of this field if it has changed and fires a value change
     * event. If the value originates from the client and this field is
     * read-only, does nothing. Invokes {@link #doSetValue(Object) doSetValue}
     * to actually store the value.
     *
     * @param value
     *            the new value to set
     * @param userOriginated
     *            {@code true} if this event originates from the client,
     *            {@code false} otherwise.
     * @return <code>true</code> if the value was updated, <code>false</code>
     *         otherwise
     */
    protected boolean setValue(T value, boolean userOriginated) {
        if (userOriginated && isReadOnly()) {
            return false;
        }
        if (Objects.equals(value, getValue())) {
            return false;
        }
        doSetValue(value);
        if (!userOriginated) {
            markAsDirty();
        }
        fireEvent(createValueChange(userOriginated));

        return true;
    }

    /**
     * Sets the value of this field. May do sanitization or throw
     * {@code IllegalArgumentException} if the value is invalid. Typically saves
     * the value to shared state.
     *
     * @param value
     *            the new value of the field
     * @throws IllegalArgumentException
     *             if the value is invalid
     */
    protected abstract void doSetValue(T value);

    /**
     * Returns a new value change event instance.
     *
     * @param userOriginated
     *            {@code true} if this event originates from the client,
     *            {@code false} otherwise.
     * @return the new event
     */
    protected ValueChangeEvent<T> createValueChange(boolean userOriginated) {
        return new ValueChangeEvent<>(this, userOriginated);
    }

    @Override
    protected AbstractFieldState getState() {
        return (AbstractFieldState) super.getState();
    }

    @Override
    protected AbstractFieldState getState(boolean markAsDirty) {
        return (AbstractFieldState) super.getState(markAsDirty);
    }

    @Override
    public void focus() {
        super.focus();
    }

    @Override
    public int getTabIndex() {
        return getState(false).tabIndex;
    }

    @Override
    public void setTabIndex(int tabIndex) {
        getState().tabIndex = tabIndex;
    }

    @Override
    public void setRequired(boolean required) {
        getState().required = required;
    }

    @Override
    public boolean isRequired() {
        return getState(false).required;
    }
}