diff options
author | Henri Sara <hesara@vaadin.com> | 2011-12-08 10:38:53 +0200 |
---|---|---|
committer | Henri Sara <hesara@vaadin.com> | 2011-12-08 10:38:53 +0200 |
commit | cd86b2c97ccdd452bda4157578ffcd29cab3d645 (patch) | |
tree | 96f7a1e3274c99b57c7dd681c08b4b937dc595e4 /src/com/vaadin/ui | |
parent | 9cddb80d22ac9074032bde2880623ddfbdc60097 (diff) | |
download | vaadin-framework-cd86b2c97ccdd452bda4157578ffcd29cab3d645.tar.gz vaadin-framework-cd86b2c97ccdd452bda4157578ffcd29cab3d645.zip |
Add CustomField component in Vaadin core (#3718)
CustomField component, related changes in AbstractField and some
tests/examples added to Vaadin core.
Diffstat (limited to 'src/com/vaadin/ui')
-rw-r--r-- | src/com/vaadin/ui/AbstractField.java | 19 | ||||
-rw-r--r-- | src/com/vaadin/ui/CustomField.java | 242 |
2 files changed, 256 insertions, 5 deletions
diff --git a/src/com/vaadin/ui/AbstractField.java b/src/com/vaadin/ui/AbstractField.java index 73760d9247..44fb71ac84 100644 --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@ -328,7 +328,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements setModified(false); // If the new value differs from the previous one - if (!equals(newFieldValue, value)) { + if (!equals(newFieldValue, getInternalValue())) { setInternalValue(newFieldValue); fireValueChange(false); } else if (wasModified) { @@ -355,7 +355,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements private T getFieldValue() { // Give the value from abstract buffers if the field if possible if (dataSource == null || !isReadThrough() || isModified()) { - return value; + return getInternalValue(); } // There is no buffered value so use whatever the data model provides @@ -521,7 +521,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements protected void setValue(T newFieldValue, boolean repaintIsNotNeeded) throws Property.ReadOnlyException, Property.ConversionException { - if (!equals(newFieldValue, value)) { + if (!equals(newFieldValue, getInternalValue())) { // Read only fields can not be changed if (isReadOnly()) { @@ -646,7 +646,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements public void setPropertyDataSource(Property newDataSource) { // Saves the old value - final Object oldValue = value; + final Object oldValue = getInternalValue(); // Stops listening the old data source changes if (dataSource != null @@ -705,6 +705,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements } // Fires value change if the value has changed + T value = getInternalValue(); if ((value != oldValue) && ((value != null && !value.equals(oldValue)) || value == null)) { fireValueChange(false); @@ -1143,7 +1144,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements if (isReadThrough()) { if (committingValueToDataSource) { boolean propertyNotifiesOfTheBufferedValue = equals(event - .getProperty().getValue(), value); + .getProperty().getValue(), getInternalValue()); if (!propertyNotifiesOfTheBufferedValue) { /* * Property (or chained property like PropertyFormatter) now @@ -1202,6 +1203,14 @@ public abstract class AbstractField<T> extends AbstractComponent implements } /** + * + * @return + */ + protected T getInternalValue() { + return value; + } + + /** * Sets the internal field value. This is purely used by AbstractField to * change the internal Field value. It does not trigger valuechange events. * It can be overridden by the inheriting classes to update all dependent diff --git a/src/com/vaadin/ui/CustomField.java b/src/com/vaadin/ui/CustomField.java new file mode 100644 index 0000000000..eb4cdc3ef6 --- /dev/null +++ b/src/com/vaadin/ui/CustomField.java @@ -0,0 +1,242 @@ +package com.vaadin.ui; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Iterator; + +import com.vaadin.data.Property; +import com.vaadin.terminal.PaintException; +import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.gwt.client.ui.VCustomComponent; + +/** + * A {@link Field} whose UI content can be constructed by the user, enabling the + * creation of e.g. form fields by composing Vaadin components. Customization of + * both the visual presentation and the logic of the field is possible. + * + * Subclasses must implement {@link #getType()} and {@link #createContent()}. + * + * Most custom fields can simply compose a user interface that calls the methods + * {@link #setInternalValue(Object)} and {@link #getInternalValue()} when + * necessary. + * + * It is also possible to override {@link #commit()}, + * {@link #setPropertyDataSource(Property)} and other logic of the field. + * + * @since 7.0 + */ +@ClientWidget(VCustomComponent.class) +public abstract class CustomField<T> extends AbstractField<T> implements + ComponentContainer { + + /** + * The root component implementing the custom component. + */ + private Component root = null; + + /** + * Constructs a new custom field. + * + * <p> + * The component is implemented by wrapping the methods of the composition + * root component given as parameter. The composition root must be set + * before the component can be used. + * </p> + */ + public CustomField() { + // expand horizontally by default + setWidth(100, UNITS_PERCENTAGE); + } + + /** + * Constructs the content and notifies it that the {@link CustomField} is + * attached to a window. + * + * @see com.vaadin.ui.Component#attach() + */ + @Override + public void attach() { + root = getContent(); + super.attach(); + getContent().setParent(this); + getContent().attach(); + + fireComponentAttachEvent(getContent()); + } + + /** + * Notifies the content that the {@link CustomField} is detached from a + * window. + * + * @see com.vaadin.ui.Component#detach() + */ + @Override + public void detach() { + super.detach(); + getContent().detach(); + } + + @Override + public void paintContent(PaintTarget target) throws PaintException { + if (getContent() == null) { + throw new IllegalStateException( + "Content component or layout of the field must be set before the " + + getClass().getName() + " can be painted"); + } + + getContent().paint(target); + } + + /** + * Returns the content of the + * + * @return + */ + protected Component getContent() { + if (null == root) { + root = createContent(); + } + return root; + } + + /** + * Create the content component or layout for the field. Subclasses of + * {@link CustomField} should implement this method. + * + * Note that this method is called when the CustomField is attached to a + * layout or when {@link #getContent()} is called explicitly for the first + * time. It is only called once for a {@link CustomField}. + * + * @return + */ + protected abstract Component createContent(); + + private void requestContentRepaint() { + if (getParent() == null) { + // skip repaint - not yet attached + return; + } + if (getContent() instanceof ComponentContainer) { + ((ComponentContainer) getContent()).requestRepaintAll(); + } else { + getContent().requestRepaint(); + } + } + + // Size related methods + // TODO might not be necessary to override but following the pattern from + // AbstractComponentContainer + + @Override + public void setHeight(float height, int unit) { + super.setHeight(height, unit); + requestContentRepaint(); + } + + @Override + public void setWidth(float height, int unit) { + super.setWidth(height, unit); + requestContentRepaint(); + } + + // ComponentContainer methods + + private class ComponentIterator implements Iterator<Component>, + Serializable { + boolean first = getContent() != null; + + public boolean hasNext() { + return first; + } + + public Component next() { + first = false; + return getContent(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public Iterator<Component> getComponentIterator() { + return new ComponentIterator(); + } + + public int getComponentCount() { + return (null != getContent()) ? 1 : 0; + } + + public void requestRepaintAll() { + requestRepaint(); + + requestContentRepaint(); + } + + /** + * Fires the component attached event. This should be called by the + * addComponent methods after the component have been added to this + * container. + * + * @param component + * the component that has been added to this container. + */ + protected void fireComponentAttachEvent(Component component) { + fireEvent(new ComponentAttachEvent(this, component)); + } + + // TODO remove these methods when ComponentContainer interface is cleaned up + + public void addComponent(Component c) { + throw new UnsupportedOperationException(); + } + + public void removeComponent(Component c) { + throw new UnsupportedOperationException(); + } + + public void removeAllComponents() { + throw new UnsupportedOperationException(); + } + + public void replaceComponent(Component oldComponent, Component newComponent) { + throw new UnsupportedOperationException(); + } + + public void moveComponentsFrom(ComponentContainer source) { + throw new UnsupportedOperationException(); + } + + private static final Method COMPONENT_ATTACHED_METHOD; + + static { + try { + COMPONENT_ATTACHED_METHOD = ComponentAttachListener.class + .getDeclaredMethod("componentAttachedToContainer", + new Class[] { ComponentAttachEvent.class }); + } catch (final java.lang.NoSuchMethodException e) { + // This should never happen + throw new java.lang.RuntimeException( + "Internal error finding methods in CustomField"); + } + } + + public void addListener(ComponentAttachListener listener) { + addListener(ComponentContainer.ComponentAttachEvent.class, listener, + COMPONENT_ATTACHED_METHOD); + } + + public void removeListener(ComponentAttachListener listener) { + removeListener(ComponentContainer.ComponentAttachEvent.class, listener, + COMPONENT_ATTACHED_METHOD); + } + + public void addListener(ComponentDetachListener listener) { + // content never detached + } + + public void removeListener(ComponentDetachListener listener) { + // content never detached + } + +} |