summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJohannes Dahlström <johannesd@vaadin.com>2016-08-04 22:18:57 +0300
committerJohannes Dahlström <johannesd@vaadin.com>2016-08-05 22:10:56 +0300
commit5d864b14b385e303f6796bd55969ac5c44124ae0 (patch)
treeee8c7da7787d1581945559ab92342dec0f2cf166 /server
parentf6f111b88fcf65c9d75f36daaa046dd26efc68e5 (diff)
downloadvaadin-framework-5d864b14b385e303f6796bd55969ac5c44124ae0.tar.gz
vaadin-framework-5d864b14b385e303f6796bd55969ac5c44124ae0.zip
Implement new simple AbstractField
Change-Id: I9addcf8bec802967b1dfa39512dd140b8a4e4a25
Diffstat (limited to 'server')
-rw-r--r--server/src/main/java/com/vaadin/data/HasValue.java153
-rw-r--r--server/src/main/java/com/vaadin/event/ConnectorEvent.java18
-rw-r--r--server/src/main/java/com/vaadin/event/Registration.java33
-rw-r--r--server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java51
-rw-r--r--server/src/main/java/com/vaadin/legacy/ui/LegacyField.java34
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractField.java182
-rw-r--r--server/src/main/java/com/vaadin/ui/Component.java39
-rw-r--r--server/src/test/java/com/vaadin/ui/AbstractFieldTest.java122
8 files changed, 582 insertions, 50 deletions
diff --git a/server/src/main/java/com/vaadin/data/HasValue.java b/server/src/main/java/com/vaadin/data/HasValue.java
new file mode 100644
index 0000000000..6032fcb2be
--- /dev/null
+++ b/server/src/main/java/com/vaadin/data/HasValue.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2000-2014 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.data;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+import com.vaadin.event.ConnectorEvent;
+import com.vaadin.event.ConnectorEventListener;
+import com.vaadin.event.Registration;
+import com.vaadin.server.ClientConnector;
+
+/**
+ * A generic interface for field components and other user interface objects
+ * that have a user-editable value. Emits change events whenever the value is
+ * changed, either by the user or programmatically.
+ *
+ * @since
+ * @param <V>
+ * the value type
+ */
+public interface HasValue<V> extends Serializable {
+
+ /**
+ * An event fired when the value of a {@code HasValue} changes.
+ *
+ * @param <V>
+ * the value type
+ */
+ public class ValueChange<V> extends ConnectorEvent {
+
+ private final V value;
+ private final boolean userOriginated;
+
+ /**
+ * Creates a new {@code ValueChange} event containing the current value
+ * of the given value-bearing source connector.
+ *
+ * @param <C>
+ * the type of the source connector
+ * @param source
+ * the source connector bearing the value, not null
+ * @param userOriginated
+ * {@code true} if this event originates from the client,
+ * {@code false} otherwise.
+ */
+ public <C extends ClientConnector & HasValue<V>> ValueChange(
+ C source, boolean userOriginated) {
+ super(source);
+ this.value = source.getValue();
+ this.userOriginated = userOriginated;
+ }
+
+ /**
+ * Returns the new value of the source connector.
+ *
+ * @return the new value
+ */
+ public V getValue() {
+ return value;
+ }
+
+ /**
+ * Returns whether this event was triggered by user interaction, on the
+ * client side, or programmatically, on the server side.
+ *
+ * @return {@code true} if this event originates from the client,
+ * {@code false} otherwise.
+ */
+ public boolean isUserOriginated() {
+ return userOriginated;
+ }
+ }
+
+ /**
+ * A listener for value change events.
+ *
+ * @param <V>
+ * the value type
+ *
+ * @see ValueChange
+ * @see Registration
+ */
+ @FunctionalInterface
+ public interface ValueChangeListener<V> extends Consumer<ValueChange<V>>,
+ ConnectorEventListener {
+
+ /**
+ * Invoked when this listener receives a value change event from an
+ * event source to which it has been added.
+ *
+ * @param event
+ * the received event, not null
+ */
+ // In addition to customizing the Javadoc, this override is needed
+ // to make ReflectTools.findMethod work as expected. It uses
+ // Class.getDeclaredMethod, but even if it used getMethod instead, the
+ // superinterface argument type is Object, not Event, after type
+ // erasure.
+ @Override
+ public void accept(ValueChange<V> event);
+ }
+
+ /**
+ * Sets the value of this object. If the new value is not equal to
+ * {@code getValue()}, fires a value change event. May throw
+ * {@code IllegalArgumentException} if the value is not acceptable.
+ * <p>
+ * <i>Implementation note:</i> the implementing class should document
+ * whether null values are accepted or not.
+ *
+ * @param value
+ * the new value
+ * @throws IllegalArgumentException
+ * if the value is invalid
+ */
+ public void setValue(V value);
+
+ /**
+ * Returns the current value of this object.
+ * <p>
+ * <i>Implementation note:</i> the implementing class should document
+ * whether null values may be returned or not.
+ *
+ * @return the current value
+ */
+ public V getValue();
+
+ /**
+ * Adds an {@link ValueChangeListener}. The listener is called when the
+ * value of this {@code hasValue} is changed either by the user or
+ * programmatically.
+ *
+ * @param listener
+ * the value change listener, not null
+ * @return a registration for the listener
+ */
+ public Registration addValueChangeListener(
+ ValueChangeListener<? super V> listener);
+}
diff --git a/server/src/main/java/com/vaadin/event/ConnectorEvent.java b/server/src/main/java/com/vaadin/event/ConnectorEvent.java
index 3dc73b864a..9e9d3b00a6 100644
--- a/server/src/main/java/com/vaadin/event/ConnectorEvent.java
+++ b/server/src/main/java/com/vaadin/event/ConnectorEvent.java
@@ -20,11 +20,29 @@ import java.util.EventObject;
import com.vaadin.server.ClientConnector;
+/**
+ * A base class for user interface events fired by connectors.
+ *
+ * @author Vaadin Ltd.
+ * @since 7.0
+ */
public abstract class ConnectorEvent extends EventObject {
+
+ /**
+ * Creates a new event fired by the given source.
+ *
+ * @param source
+ * the source connector
+ */
public ConnectorEvent(ClientConnector source) {
super(source);
}
+ /**
+ * Returns the connector that fired the event.
+ *
+ * @return the source connector
+ */
public ClientConnector getConnector() {
return (ClientConnector) getSource();
}
diff --git a/server/src/main/java/com/vaadin/event/Registration.java b/server/src/main/java/com/vaadin/event/Registration.java
new file mode 100644
index 0000000000..ab393e4750
--- /dev/null
+++ b/server/src/main/java/com/vaadin/event/Registration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2014 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.event;
+
+import java.io.Serializable;
+
+/**
+ * A registration object for removing an event listener added to a source.
+ *
+ * @author Vaadin Ltd.
+ * @since
+ */
+@FunctionalInterface
+public interface Registration extends Serializable {
+
+ /**
+ * Removes the associated listener from the event source.
+ */
+ public void remove();
+}
diff --git a/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java b/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java
index d1b013a64a..8cce999371 100644
--- a/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java
+++ b/server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java
@@ -62,10 +62,10 @@ import com.vaadin.ui.declarative.DesignContext;
* </p>
*
* <p>
- * LegacyAbstractField also provides the {@link com.vaadin.data.Buffered} interface
- * for buffering the data source value. By default the LegacyField is in write
- * through-mode and {@link #setWriteThrough(boolean)}should be called to enable
- * buffering.
+ * LegacyAbstractField also provides the {@link com.vaadin.data.Buffered}
+ * interface for buffering the data source value. By default the LegacyField is
+ * in write through-mode and {@link #setWriteThrough(boolean)}should be called
+ * to enable buffering.
* </p>
*
* <p>
@@ -75,10 +75,17 @@ import com.vaadin.ui.declarative.DesignContext;
*
* @author Vaadin Ltd.
* @since 3.0
+ *
+ * @deprecated This class is, apart from the rename, identical to the Vaadin 7
+ * {@code com.vaadin.ui.AbstractField}. It is provided for
+ * compatibility and migration purposes. As of 8.0, new field
+ * implementations should extend the new
+ * {@link com.vaadin.ui.AbstractField} instead.
*/
@SuppressWarnings("serial")
-public abstract class LegacyAbstractField<T> extends AbstractComponent implements
- LegacyField<T>, Property.ReadOnlyStatusChangeListener,
+@Deprecated
+public abstract class LegacyAbstractField<T> extends AbstractComponent
+ implements LegacyField<T>, Property.ReadOnlyStatusChangeListener,
Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier {
/* Private members */
@@ -187,11 +194,11 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
}
/**
- * Returns the type of the LegacyField. The methods <code>getValue</code> and
- * <code>setValue</code> must be compatible with this type: one must be able
- * to safely cast the value returned from <code>getValue</code> to the given
- * type and pass any variable assignable to this type as an argument to
- * <code>setValue</code>.
+ * Returns the type of the LegacyField. The methods <code>getValue</code>
+ * and <code>setValue</code> must be compatible with this type: one must be
+ * able to safely cast the value returned from <code>getValue</code> to the
+ * given type and pass any variable assignable to this type as an argument
+ * to <code>setValue</code>.
*
* @return the type of the LegacyField
*/
@@ -834,9 +841,9 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
* Returns the current value (as returned by {@link #getValue()}) converted
* to the data source type.
* <p>
- * This returns the same as {@link LegacyAbstractField#getValue()} if no converter
- * has been set. The value is not necessarily the same as the data source
- * value e.g. if the field is in buffered mode and has been modified.
+ * This returns the same as {@link LegacyAbstractField#getValue()} if no
+ * converter has been set. The value is not necessarily the same as the data
+ * source value e.g. if the field is in buffered mode and has been modified.
* </p>
*
* @return The converted value that is compatible with the data source type
@@ -870,7 +877,7 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
@Override
public void addValidator(Validator validator) {
if (validators == null) {
- validators = new LinkedList<Validator>();
+ validators = new LinkedList<>();
}
validators.add(validator);
markAsDirty();
@@ -993,7 +1000,7 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
}
}
- List<InvalidValueException> validationExceptions = new ArrayList<InvalidValueException>();
+ List<InvalidValueException> validationExceptions = new ArrayList<>();
if (validators != null) {
// Gets all the validation errors
for (Validator v : validators) {
@@ -1384,10 +1391,10 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
}
/**
- * Sets the internal field value. This is purely used by LegacyAbstractField to
- * change the internal LegacyField value. It does not trigger valuechange events.
- * It can be overridden by the inheriting classes to update all dependent
- * variables.
+ * Sets the internal field value. This is purely used by LegacyAbstractField
+ * to change the internal LegacyField value. It does not trigger valuechange
+ * events. It can be overridden by the inheriting classes to update all
+ * dependent variables.
*
* Subclasses can also override {@link #getInternalValue()} if necessary.
*
@@ -1619,8 +1626,8 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
/**
* A ready-made {@link ShortcutListener} that focuses the given
- * {@link Focusable} (usually a {@link LegacyField}) when the keyboard shortcut is
- * invoked.
+ * {@link Focusable} (usually a {@link LegacyField}) when the keyboard
+ * shortcut is invoked.
*
*/
public static class FocusShortcut extends ShortcutListener {
diff --git a/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java b/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java
index a3ac84871d..ff38cf2859 100644
--- a/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java
+++ b/server/src/main/java/com/vaadin/legacy/ui/LegacyField.java
@@ -17,32 +17,37 @@
package com.vaadin.legacy.ui;
import com.vaadin.data.BufferedValidatable;
+import com.vaadin.data.HasValue.ValueChange;
import com.vaadin.data.Property;
import com.vaadin.ui.Component;
-import com.vaadin.ui.Component.Event;
import com.vaadin.ui.Component.Focusable;
/**
- * LegacyField interface is implemented by all classes (field components) that have a
- * value that the user can change through the user interface.
+ * LegacyField interface is implemented by all legacy field components that have
+ * a value that the user can change through the user interface.
*
- * LegacyField components are built upon the framework defined in the LegacyField interface
- * and the {@link com.vaadin.LegacyAbstractField} base class.
+ * LegacyField components are built upon the framework defined in the
+ * LegacyField interface and the {@link com.vaadin.LegacyAbstractField} base
+ * class.
*
* The LegacyField interface inherits the {@link com.vaadin.ui.Component}
* superinterface and also the {@link com.vaadin.ui.Property} interface to have
* a value for the field.
*
- *
* @author Vaadin Ltd.
*
- * @param T
+ * @param <T>
* the type of values in the field, which might not be the same type
* as that of the data source if converters are used
*
- * @author IT Mill Ltd.
+ * @deprecated This interface is, apart from the rename, identical to the Vaadin
+ * 7 {@code com.vaadin.ui.Field}. It is provided for compatibility
+ * and migration purposes. As of 8.0, new field components should
+ * extend {@link com.vaadin.ui.AbstractField} instead.
*/
-public interface LegacyField<T> extends Component, BufferedValidatable, Property<T>,
+@Deprecated
+public interface LegacyField<T> extends Component, BufferedValidatable,
+ Property<T>,
Property.ValueChangeNotifier, Property.ValueChangeListener,
Property.Editor, Focusable {
@@ -85,14 +90,18 @@ public interface LegacyField<T> extends Component, BufferedValidatable, Property
public String getRequiredError();
/**
- * An <code>Event</code> object specifying the LegacyField whose value has been
- * changed.
+ * An <code>Event</code> object specifying the LegacyField whose value has
+ * been changed.
*
* @author Vaadin Ltd.
* @since 3.0
+ *
+ * @deprecated As of 8.0, replaced by {@link ValueChange}.
*/
+ @Deprecated
@SuppressWarnings("serial")
- public static class ValueChangeEvent extends Component.Event implements
+ public static class ValueChangeEvent extends
+ Component.Event implements
Property.ValueChangeEvent {
/**
@@ -114,6 +123,7 @@ public interface LegacyField<T> extends Component, BufferedValidatable, Property
public Property getProperty() {
return (Property) getSource();
}
+
}
/**
diff --git a/server/src/main/java/com/vaadin/ui/AbstractField.java b/server/src/main/java/com/vaadin/ui/AbstractField.java
new file mode 100644
index 0000000000..7a9d1b9b0c
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/AbstractField.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2000-2014 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.HasValue;
+import com.vaadin.event.Registration;
+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.legacy.ui.LegacyAbstractField}, for compatibility and
+ * migration purposes.
+ *
+ * @author Vaadin Ltd.
+ * @since
+ *
+ * @param <T>
+ * the input value type
+ */
+public abstract class AbstractField<T> extends AbstractComponent
+ implements HasValue<T> {
+
+ @Deprecated
+ private static final Method VALUE_CHANGE_METHOD = ReflectTools.findMethod(
+ ValueChangeListener.class, "accept", ValueChange.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<? super T> listener) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ addListener(ValueChange.class, listener, VALUE_CHANGE_METHOD);
+ return () -> removeListener(ValueChange.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
+ * @return {@code true} if this event originates from the client,
+ * {@code false} otherwise.
+ */
+ protected void setValue(T value, boolean userOriginated) {
+ if (userOriginated && isReadOnly()) {
+ return;
+ }
+ if (Objects.equals(value, getValue())) {
+ return;
+ }
+ doSetValue(value);
+ if (!userOriginated) {
+ markAsDirty();
+ }
+ fireEvent(createValueChange(userOriginated));
+ }
+
+ /**
+ * 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 ValueChange<T> createValueChange(boolean userOriginated) {
+ return new ValueChange<>(this, userOriginated);
+ }
+}
diff --git a/server/src/main/java/com/vaadin/ui/Component.java b/server/src/main/java/com/vaadin/ui/Component.java
index 0e8b385128..9bfd823fc4 100644
--- a/server/src/main/java/com/vaadin/ui/Component.java
+++ b/server/src/main/java/com/vaadin/ui/Component.java
@@ -379,10 +379,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
/**
* Tests whether the component is in the read-only mode. The user can not
- * change the value of a read-only component. As only {@link LegacyField}
- * components normally have a value that can be input or changed by the
- * user, this is mostly relevant only to field components, though not
- * restricted to them.
+ * change the value of a read-only component. As only {@link AbstractField}
+ * or {@link LegacyField} components normally have a value that can be input
+ * or changed by the user, this is mostly relevant only to field components,
+ * though not restricted to them.
*
* <p>
* Notice that the read-only mode only affects whether the user can change
@@ -406,9 +406,9 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* can not change the value of a read-only component.
*
* <p>
- * As only {@link LegacyField} components normally have a value that can be input
- * or changed by the user, this is mostly relevant only to field components,
- * though not restricted to them.
+ * As only {@link AbstractField} or{@link LegacyField} components normally
+ * have a value that can be input or changed by the user, this is mostly
+ * relevant only to field components, though not restricted to them.
* </p>
*
* <p>
@@ -596,7 +596,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* public class AttachExample extends CustomComponent {
* public AttachExample() {
* // ERROR: We can't access the application object yet.
- * ClassResource r = new ClassResource(&quot;smiley.jpg&quot;, getApplication());
+ * ClassResource r = new ClassResource(&quot;smiley.jpg&quot;,
+ * getApplication());
* Embedded image = new Embedded(&quot;Image:&quot;, r);
* setCompositionRoot(image);
* }
@@ -622,7 +623,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* super.attach(); // Must call.
*
* // Now we know who ultimately owns us.
- * ClassResource r = new ClassResource(&quot;smiley.jpg&quot;, getApplication());
+ * ClassResource r = new ClassResource(&quot;smiley.jpg&quot;,
+ * getApplication());
* Embedded image = new Embedded(&quot;Image:&quot;, r);
* setCompositionRoot(image);
* }
@@ -879,7 +881,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* getWindow().showNotification(&quot;Click!&quot;);
*
* // Display source component and event class names
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass()
+ * .getName()
* + &quot;: &quot; + event.getClass().getName());
* }
* }
@@ -904,11 +907,13 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* <pre>
* public void componentEvent(Event event) {
* // Act according to the source of the event
- * if (event.getSource() == ok &amp;&amp; event.getClass() == Button.ClickEvent.class)
+ * if (event.getSource() == ok &amp;&amp; event
+ * .getClass() == Button.ClickEvent.class)
* getWindow().showNotification(&quot;Click!&quot;);
*
* // Display source component and event class names
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass()
+ * .getName()
* + &quot;: &quot; + event.getClass().getName());
* }
* </pre>
@@ -956,7 +961,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* if (event.getSource() == ok)
* getWindow().showNotification(&quot;Click!&quot;);
*
- * status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+ * status.setValue(&quot;Event from &quot; + event.getSource().getClass()
+ * .getName()
* + &quot;: &quot; + event.getClass().getName());
* }
* }
@@ -1028,9 +1034,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
* <p>
* Focus can be set with {@link #focus()}. This interface does not provide
* an accessor that would allow finding out the currently focused component;
- * focus information can be acquired for some (but not all) {@link LegacyField}
- * components through the {@link com.vaadin.event.FieldEvents.FocusListener}
- * and {@link com.vaadin.event.FieldEvents.BlurListener} interfaces.
+ * focus information can be acquired for some (but not all)
+ * {@link LegacyField} components through the
+ * {@link com.vaadin.event.FieldEvents.FocusListener} and
+ * {@link com.vaadin.event.FieldEvents.BlurListener} interfaces.
* </p>
*
* @see FieldEvents
diff --git a/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java b/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java
new file mode 100644
index 0000000000..bdd63caaa4
--- /dev/null
+++ b/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java
@@ -0,0 +1,122 @@
+package com.vaadin.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.easymock.EasyMockSupport;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.HasValue.ValueChange;
+import com.vaadin.data.HasValue.ValueChangeListener;
+import com.vaadin.server.ClientConnector;
+
+public class AbstractFieldTest extends EasyMockSupport {
+
+ class TextField extends AbstractField<String> {
+
+ String value = "";
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ protected void doSetValue(String value) {
+ this.value = value;
+ }
+ }
+
+ TextField field;
+
+ ValueChangeListener<String> l;
+ Capture<ValueChange<String>> capture;
+
+ @Before
+ public void setUp() {
+ field = new TextField();
+ l = mockListener();
+ capture = new Capture<>();
+ }
+
+ @Test
+ public void readOnlyFieldAcceptsValueChangeFromServer() {
+ field.setReadOnly(true);
+ field.setValue("foo");
+ assertEquals("foo", field.getValue());
+ }
+
+ @Test
+ public void readOnlyFieldIgnoresValueChangeFromClient() {
+ field.setReadOnly(true);
+ field.setValue("bar", true);
+ assertEquals("", field.getValue());
+ }
+
+ @Test
+ public void valueChangeListenerInvoked() {
+ l.accept(EasyMock.capture(capture));
+ replayAll();
+
+ field.setValue("foo");
+ field.addValueChangeListener(l);
+ field.setValue("bar");
+
+ assertEventEquals(capture.getValue(), "bar", field, false);
+
+ verifyAll();
+ }
+
+ @Test
+ public void valueChangeListenerInvokedFromClient() {
+ l.accept(EasyMock.capture(capture));
+ replayAll();
+
+ field.setValue("foo");
+ field.addValueChangeListener(l);
+ field.setValue("bar", true);
+
+ assertEventEquals(capture.getValue(), "bar", field, true);
+
+ verifyAll();
+ }
+
+ @Test
+ public void valueChangeListenerNotInvokedIfValueUnchanged() {
+ // expect zero invocations of l
+ replayAll();
+
+ field.setValue("foo");
+ field.addValueChangeListener(l);
+ field.setValue("foo");
+
+ verifyAll();
+ }
+
+ @Test
+ public void valueChangeListenerNotInvokedAfterRemove() {
+ // expect zero invocations of l
+ replayAll();
+
+ field.addValueChangeListener(l).remove();
+ field.setValue("foo");
+
+ verifyAll();
+ }
+
+ @SuppressWarnings("unchecked")
+ private ValueChangeListener<String> mockListener() {
+ return createStrictMock(ValueChangeListener.class);
+ }
+
+ private void assertEventEquals(ValueChange<String> e, String value,
+ ClientConnector source, boolean userOriginated) {
+ assertEquals("event value", value, e.getValue());
+ assertSame("event source", source, e.getSource());
+ assertSame("event source connector", source, e.getConnector());
+ assertEquals("event from user", userOriginated, e.isUserOriginated());
+ }
+}