@@ -91,6 +91,18 @@ binder.bind(nameField, | |||
} | |||
}); | |||
---- | |||
== Binding non-modifiable Data | |||
Non-modifiable data can be also bound to any component or component property with [classname]#ReadOnlyHasValue# helper class. | |||
For example, `Panel` caption can display a person full name: | |||
[source, java] | |||
---- | |||
Panel infoPanel = new Panel(); | |||
ReadOnlyHasValue<Person> panelTitle = new ReadOnlyHasValue<>( | |||
person -> infoPanel.setCaption(person.getLastName() + ", " + person.getFirstName())); | |||
binder.forField(panelTitle).bind(person -> person, null); | |||
---- | |||
== Validating and Converting User Input | |||
@@ -0,0 +1,119 @@ | |||
/* | |||
* 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.data; | |||
import com.vaadin.server.SerializableConsumer; | |||
import com.vaadin.shared.Registration; | |||
import com.vaadin.ui.Label; | |||
import java.io.Serializable; | |||
import java.util.LinkedHashSet; | |||
import java.util.Objects; | |||
/** | |||
* Generic {@link HasValue} to use any type of component with Vaadin data binding. | |||
* <p> | |||
* Example: | |||
* <pre> | |||
* Label label = new Label(); | |||
* ReadOnlyHasValue<String> hasValue = new ReadOnlyHasValue<>(label::setCaption); | |||
* binder.forField(hasValue).bind(SomeBean::getName); | |||
* </pre> | |||
* | |||
* @param <V> the value type | |||
* @since | |||
*/ | |||
public class ReadOnlyHasValue<V> implements HasValue<V>, Serializable { | |||
private V value; | |||
private final SerializableConsumer<V> valueProcessor; | |||
private final V emptyValue; | |||
private LinkedHashSet<ValueChangeListener<V>> listenerList; | |||
/** | |||
* Creates new {@code ReadOnlyHasValue} | |||
* | |||
* @param valueProcessor the value valueProcessor, e.g. {@link Label#setValue} | |||
* @param emptyValue the value to be used as empty, {@code null} by default | |||
*/ | |||
public ReadOnlyHasValue(SerializableConsumer<V> valueProcessor, V emptyValue) { | |||
this.valueProcessor = valueProcessor; | |||
this.emptyValue = emptyValue; | |||
} | |||
/** | |||
* Creates new {@code ReadOnlyHasValue} with {@code null} as an empty value. | |||
* | |||
* @param valueProcessor the value valueProcessor, e.g. {@link Label#setValue} | |||
*/ | |||
public ReadOnlyHasValue(SerializableConsumer<V> valueProcessor) { | |||
this(valueProcessor,null); | |||
} | |||
@Override | |||
public void setValue(V value) { | |||
V oldValue = this.value; | |||
this.value = value; | |||
valueProcessor.accept(value); | |||
if (listenerList != null && ! Objects.equals(oldValue, value)) { | |||
for (ValueChangeListener<V> valueChangeListener : listenerList) { | |||
valueChangeListener.valueChange( | |||
new ValueChangeEvent<>(null, this, oldValue, false)); | |||
} | |||
} | |||
} | |||
@Override | |||
public V getValue() { | |||
return value; | |||
} | |||
@Override | |||
public Registration addValueChangeListener( | |||
ValueChangeListener<V> listener) { | |||
Objects.requireNonNull(listener, "Listener must not be null."); | |||
if (listenerList == null) { | |||
listenerList = new LinkedHashSet<>(); | |||
} | |||
listenerList.add(listener); | |||
return () -> listenerList.remove(listener); | |||
} | |||
@Override | |||
public boolean isRequiredIndicatorVisible() { | |||
return false; | |||
} | |||
@Override | |||
public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) { | |||
if (requiredIndicatorVisible) throw new IllegalArgumentException("Not Writable"); | |||
} | |||
@Override | |||
public void setReadOnly(boolean readOnly) { | |||
if (!readOnly) throw new IllegalArgumentException("Not Writable"); | |||
} | |||
@Override | |||
public boolean isReadOnly() { | |||
return true; | |||
} | |||
@Override | |||
public V getEmptyValue() { | |||
return emptyValue; | |||
} | |||
} |
@@ -0,0 +1,116 @@ | |||
package com.vaadin.data; | |||
import com.vaadin.shared.Registration; | |||
import com.vaadin.ui.Label; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.util.Objects; | |||
import static org.junit.Assert.*; | |||
public class ReadOnlyHasValueTest { | |||
private static final String SAY_SOMETHING = "Say something"; | |||
private static final String SAY_SOMETHING_ELSE = "Say something else"; | |||
private static final String NO_VALUE = "-no-value-"; | |||
private Label label; | |||
private ReadOnlyHasValue<String> hasValue; | |||
@Before | |||
public void setup() { | |||
label = new Label(); | |||
hasValue = new ReadOnlyHasValue<>(label::setCaption); | |||
} | |||
@Test | |||
public void testBase() { | |||
hasValue.setReadOnly(true); | |||
hasValue.setRequiredIndicatorVisible(false); | |||
Registration registration = hasValue.addValueChangeListener(e -> { | |||
}); | |||
registration.remove(); | |||
hasValue.setValue(SAY_SOMETHING); | |||
assertEquals(SAY_SOMETHING, hasValue.getValue()); | |||
assertEquals(SAY_SOMETHING, label.getCaption()); | |||
hasValue.setValue(SAY_SOMETHING_ELSE); | |||
assertEquals(SAY_SOMETHING_ELSE, hasValue.getValue()); | |||
assertEquals(SAY_SOMETHING_ELSE, label.getCaption()); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void testRO() { | |||
hasValue.setReadOnly(false); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void testIndicator() { | |||
hasValue.setRequiredIndicatorVisible(true); | |||
} | |||
@Test | |||
public void testBind() { | |||
Binder<Bean> beanBinder = new Binder<>(Bean.class); | |||
Label label = new Label(); | |||
ReadOnlyHasValue<Long> intHasValue = new ReadOnlyHasValue<>( | |||
i -> label.setValue(Objects.toString(i, ""))); | |||
beanBinder.forField(intHasValue).bind("v"); | |||
beanBinder.readBean(new Bean(42)); | |||
assertEquals("42", label.getValue()); | |||
assertEquals(42L, intHasValue.getValue().longValue()); | |||
Registration registration = intHasValue.addValueChangeListener(e -> { | |||
assertEquals(42L, e.getOldValue().longValue()); | |||
assertSame(intHasValue, e.getSource()); | |||
assertSame(null, e.getComponent()); | |||
assertSame(null, e.getComponent()); | |||
assertFalse(e.isUserOriginated()); | |||
}); | |||
beanBinder.readBean(new Bean(1984)); | |||
assertEquals("1984", label.getValue()); | |||
assertEquals(1984L, intHasValue.getValue().longValue()); | |||
registration.remove(); | |||
beanBinder.readBean(null); | |||
assertEquals("", label.getValue()); | |||
assertEquals(null, intHasValue.getValue()); | |||
} | |||
@Test | |||
public void testEmptyValue() { | |||
Binder<Bean> beanBinder = new Binder<>(Bean.class); | |||
Label label = new Label(); | |||
ReadOnlyHasValue<String> strHasValue = | |||
new ReadOnlyHasValue<>(label::setValue, NO_VALUE); | |||
beanBinder.forField(strHasValue) | |||
.withConverter(Long::parseLong,(Long i)->"" + i) | |||
.bind("v"); | |||
beanBinder.readBean(new Bean(42)); | |||
assertEquals("42", label.getValue()); | |||
beanBinder.readBean(null); | |||
assertEquals(NO_VALUE, label.getValue()); | |||
assertTrue(strHasValue.isEmpty()); | |||
} | |||
public static class Bean { | |||
public Bean(long v) { | |||
this.v = v; | |||
} | |||
private long v; | |||
public long getV() { | |||
return v; | |||
} | |||
public void setV(long v) { | |||
this.v = v; | |||
} | |||
} | |||
} |