]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement simple shared state as a Map (#8304) - work in progress
authorHenri Sara <hesara@vaadin.com>
Tue, 31 Jan 2012 12:46:15 +0000 (14:46 +0200)
committerHenri Sara <hesara@vaadin.com>
Tue, 31 Jan 2012 12:46:15 +0000 (14:46 +0200)
In this version, SharedState can return a map that will be transferred.
On the client side, shared state is deserialized from the map but not
used.

Shared state is not sent for nested components that have not been
painted yet - doing so requires refactoring of painting of components.

16 files changed:
src/com/vaadin/data/Buffered.java
src/com/vaadin/data/Validator.java
src/com/vaadin/terminal/CompositeErrorMessage.java
src/com/vaadin/terminal/Paintable.java
src/com/vaadin/terminal/SystemError.java
src/com/vaadin/terminal/UserError.java
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/client/ComponentState.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/VPaintableMap.java
src/com/vaadin/terminal/gwt/client/ValueMap.java
src/com/vaadin/terminal/gwt/client/communication/JsonDecoder.java
src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java
src/com/vaadin/terminal/gwt/client/communication/SharedState.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/JsonCodec.java
src/com/vaadin/ui/AbstractComponent.java

index 22a4b6f9f768eda1cb77e3d4b2e340fb969ce25b..f6398f585b82a7ee902626db7a0621ede40024a8 100644 (file)
@@ -11,6 +11,7 @@ import com.vaadin.terminal.ErrorMessage;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.SystemError;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 
 /**
  * <p>
@@ -332,6 +333,11 @@ public interface Buffered extends Serializable {
 
         }
 
+        public SharedState getState() {
+            // TODO implement: move relevant parts from paint() to getState()
+            return null;
+        }
+
         /* Documented in super interface */
         public void addListener(RepaintRequestListener listener) {
         }
index 36820c1caa161e3ec7f0341c56ef3bd52442ecaf..bc64594edb900ae3a26ea06af385e9bea9c9f7c6 100644 (file)
@@ -9,6 +9,7 @@ import java.io.Serializable;
 import com.vaadin.terminal.ErrorMessage;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
 
 /**
@@ -167,6 +168,11 @@ public interface Validator extends Serializable {
             target.endTag("error");
         }
 
+        public SharedState getState() {
+            // TODO implement: move relevant parts from paint() to getState()
+            return null;
+        }
+
         /**
          * Returns the message of the error in HTML.
          * 
index 94f4e3a5d5f57b12cca21b0ffa15d9f68d22eb65..2ec1fc949e0db544f5611f65338834896888cadc 100644 (file)
@@ -10,6 +10,8 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
+import com.vaadin.terminal.gwt.client.communication.SharedState;
+
 /**
  * Class for combining multiple error messages together.
  * 
@@ -131,6 +133,11 @@ public class CompositeErrorMessage implements ErrorMessage, Serializable {
         }
     }
 
+    public SharedState getState() {
+        // TODO implement: move relevant parts from paint() to getState()
+        return null;
+    }
+
     /* Documented in super interface */
     public void addListener(RepaintRequestListener listener) {
     }
index d043cb26066765ed5180e31975c1ffdd2092e20d..9026a45ca3e5a12fab786ab02517944c61170c9c 100644 (file)
@@ -7,6 +7,8 @@ package com.vaadin.terminal;
 import java.io.Serializable;
 import java.util.EventObject;
 
+import com.vaadin.terminal.gwt.client.communication.SharedState;
+
 /**
  * Interface implemented by all classes that can be painted. Classes
  * implementing this interface know how to output themselves to a UIDL stream
@@ -39,6 +41,19 @@ public interface Paintable extends java.util.EventListener, Serializable {
      */
     public void paint(PaintTarget target) throws PaintException;
 
+    /**
+     * Returns the current shared state bean for the paintable. The state (or
+     * changes to it) is communicated from the server to the client when
+     * components are painted.
+     * 
+     * Subclasses can use a more specific return type for this method.
+     * 
+     * @return shared state instance or null
+     * 
+     * @since 7.0
+     */
+    public SharedState getState();
+
     /**
      * Requests that the paintable should be repainted as soon as possible.
      */
index 7f2464c9b97b9fed59a8453a4b26c103a8ebbdf4..55e62abb0a28bd66e9ec28b952fa7810cfdba038 100644 (file)
@@ -7,6 +7,7 @@ package com.vaadin.terminal;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
 
 /**
@@ -90,6 +91,11 @@ public class SystemError extends RuntimeException implements ErrorMessage {
 
     }
 
+    public SharedState getState() {
+        // TODO implement: move relevant parts from paint() to getState()
+        return null;
+    }
+
     /**
      * Returns the message of the error in HTML.
      * 
index 8ec45ac725b3cde752eaf2e35ac4265c57de2d03..50cf32ab0cfe5752da146d7236214692ac39aa4c 100644 (file)
@@ -4,6 +4,7 @@
 
 package com.vaadin.terminal;
 
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
 
 /**
@@ -143,6 +144,11 @@ public class UserError implements ErrorMessage {
         target.endTag("error");
     }
 
+    public SharedState getState() {
+        // TODO implement: move relevant parts from paint() to getState()
+        return null;
+    }
+
     /* Documented in interface */
     public void requestRepaintRequests() {
     }
index e790c1d3fc68751dbd05abf236544cfbe8b0566b..68ec1b54340e508ac724d24b9c8a4d83ec378cc1 100644 (file)
@@ -40,8 +40,10 @@ import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage;
 import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
 import com.vaadin.terminal.gwt.client.RenderInformation.Size;
+import com.vaadin.terminal.gwt.client.communication.JsonDecoder;
 import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
 import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.client.ui.Field;
 import com.vaadin.terminal.gwt.client.ui.VAbstractPaintableWidget;
 import com.vaadin.terminal.gwt.client.ui.VContextMenu;
@@ -996,6 +998,31 @@ public class ApplicationConnection {
                     redirectTimer.schedule(1000 * sessionExpirationInterval);
                 }
 
+                // TODO implement shared state handling
+
+                // map from paintable id to its shared state instance
+                Map<String, SharedState> sharedStates = new HashMap<String, SharedState>();
+
+                // TODO Cache shared state (if any) - components might not exist
+                // TODO cleanup later: keep only used states
+                ValueMap states = json.getValueMap("state");
+                JsArrayString keyArray = states.getKeyArray();
+                for (int i = 0; i < keyArray.length(); i++) {
+                    String paintableId = keyArray.get(i);
+                    // TODO handle as a ValueMap or similar object and native
+                    // JavaScript processing?
+                    JavaScriptObject value = states
+                            .getJavaScriptObject(paintableId);
+                    // TODO implement with shared state subclasses
+                    SharedState state = GWT.create(SharedState.class);
+                    Map<String, Object> stateMap = (Map<String, Object>) JsonDecoder
+                            .convertValue(new JSONArray(value),
+                                    getPaintableMap());
+                    state.setState(stateMap);
+                    // TODO cache state to temporary stateMap
+                    sharedStates.put(paintableId, state);
+                }
+
                 // Process changes
                 JsArray<ValueMap> changes = json.getJSValueMapArray("changes");
 
diff --git a/src/com/vaadin/terminal/gwt/client/ComponentState.java b/src/com/vaadin/terminal/gwt/client/ComponentState.java
new file mode 100644 (file)
index 0000000..955833e
--- /dev/null
@@ -0,0 +1,29 @@
+/* 
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.client;
+
+import com.vaadin.terminal.gwt.client.communication.SharedState;
+
+/**
+ * Default shared state implementation for UI components.
+ * 
+ * State classes of concrete components should extend this class.
+ * 
+ * @since 7.0
+ */
+public class ComponentState extends SharedState {
+    // TODO more javadoc
+
+    // TODO constants for the state attributes for now
+    public static final String STATE_HEIGHT = "height";
+    public static final String STATE_WIDTH = "width";
+    public static final String STATE_STYLE = "style";
+    public static final String STATE_READONLY = "readonly";
+    public static final String STATE_IMMEDIATE = "immediate";
+    public static final String STATE_DISABLED = "disabled";
+    public static final String STATE_CAPTION = "caption";
+    public static final String STATE_DESCRIPTION = "description";
+
+}
index f21d85558c2776703b0ed4a64201f3a09db0aac0..f9d05e592d59d3c80a17d34e3767405955369b08 100644 (file)
@@ -236,6 +236,7 @@ public class VPaintableMap {
                     idToComponentDetail.remove(pid);\r
                     idToPaintable.remove(pid);\r
                     paintableToId.remove(paintable);\r
+                    // TODO purge shared state for pid\r
                 }\r
                 /*\r
                  * else NOP : same component has been reattached to another\r
index 8d14ef57ceff9117b87bae0822be65ea4753ac67..5deb5feb55d52dcd461296822952ee245bf72f93 100644 (file)
@@ -101,4 +101,9 @@ public final class ValueMap extends JavaScriptObject {
         return '' + this[name];
     }-*/;
 
+    native JavaScriptObject getJavaScriptObject(String name)
+    /*-{
+        return this[name];
+    }-*/;
+
 }
\ No newline at end of file
index e7fea08bbe5347d32f4f7e809d0f2b8490561445..7ed212af4903124abdcc39dd7e672ef136795fd1 100644 (file)
@@ -37,13 +37,13 @@ public class JsonDecoder {
      *            mapper between paintable ID and {@link VPaintable} objects
      * @return converted value (does not contain JSON types)
      */
-    public static Object convertVariableValue(JSONArray value,
-            VPaintableMap idMapper) {
-        return convertVariableValue(((JSONString) value.get(0)).stringValue()
-                .charAt(0), value.get(1), idMapper);
+    public static Object convertValue(JSONArray value, VPaintableMap idMapper) {
+        return convertValue(
+                ((JSONString) value.get(0)).stringValue().charAt(0),
+                value.get(1), idMapper);
     }
 
-    private static Object convertVariableValue(char variableType, Object value,
+    private static Object convertValue(char variableType, Object value,
             VPaintableMap idMapper) {
         Object val = null;
         // TODO type checks etc.
@@ -84,6 +84,10 @@ public class JsonDecoder {
             // TODO handle properly
             val = idMapper.getPaintable(String.valueOf(value));
             break;
+        case JsonEncoder.VTYPE_SHAREDSTATE:
+            val = convertMap((JSONObject) value, idMapper);
+            // TODO convert to a SharedState instance
+            break;
         }
 
         return val;
@@ -94,8 +98,7 @@ public class JsonDecoder {
         Iterator<String> it = jsonMap.keySet().iterator();
         while (it.hasNext()) {
             String key = it.next();
-            map.put(key,
-                    convertVariableValue((JSONArray) jsonMap.get(key), idMapper));
+            map.put(key, convertValue((JSONArray) jsonMap.get(key), idMapper));
         }
         return map;
     }
@@ -115,7 +118,7 @@ public class JsonDecoder {
         for (int i = 0; i < jsonArray.size(); ++i) {
             // each entry always has two elements: type and value
             JSONArray entryArray = (JSONArray) jsonArray.get(i);
-            tokens.add(convertVariableValue(entryArray, idMapper));
+            tokens.add(convertValue(entryArray, idMapper));
         }
         return tokens.toArray(new Object[tokens.size()]);
     }
index 4b26d5939f89cc6609e9850ab3c8691749016e78..61af9294d9f28d24bd69eb4c29e9b0c8ffdd8052 100644 (file)
@@ -38,6 +38,8 @@ public class JsonEncoder {
     public static final char VTYPE_ARRAY = 'a';
     public static final char VTYPE_STRINGARRAY = 'c';
     public static final char VTYPE_MAP = 'm';
+    // TODO this will be replaced by the shared state class name
+    public static final char VTYPE_SHAREDSTATE = 't';
 
     // TODO is this needed?
     public static final char VTYPE_UNDEFINED = 'u';
diff --git a/src/com/vaadin/terminal/gwt/client/communication/SharedState.java b/src/com/vaadin/terminal/gwt/client/communication/SharedState.java
new file mode 100644 (file)
index 0000000..38183ba
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.terminal.gwt.client.communication;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.gwt.core.client.GWT;
+import com.vaadin.terminal.gwt.client.ui.VAbstractPaintableWidget;
+
+/**
+ * Interface to be implemented by all shared state classes used to communicate
+ * basic information about a paintable from server to client. These typically
+ * replace most of the semi-static information sent via the paintContent() and
+ * updateFromUIDL() mechanism in Vaadin 6 (component sizes, captions, tooltips,
+ * etc.).
+ * 
+ * Shared state classes have to be declared in client side packages to be
+ * accessible both for server and client code. They can be static nested classes
+ * of the client side widget.
+ * 
+ * Shared state objects are only sent from the server to the client, and any
+ * modifications from the client should be performed via an RPC call that
+ * modifies the authoritative state on the server.
+ * 
+ * In current Vaadin versions, the whole shared state is sent every time the
+ * component is painted. Future versions may optimize this so that only the
+ * necessary (changed or missing on the client side) parts are re-sent to the
+ * client, but the client will have access to the whole state.
+ * 
+ * TODO the rest of the javadoc corresponds to the design that is not yet
+ * implemented
+ * 
+ * A shared state class should be a bean with getters and setters for each
+ * field, and should only contain simple data types, or arrays or maps of
+ * supported data types.
+ * 
+ * On the client side, SharedState instances must be created using
+ * {@link GWT#create(Class)} to let a generator create custom deserialization
+ * support for them. For most widgets,
+ * {@link VAbstractPaintableWidget#createSharedState()} method should be
+ * overridden to create a shared state instance of the correct type using
+ * {@link GWT#create(Class)}.
+ * 
+ * Subclasses of a paintable using shared state should also provide a subclass
+ * of the shared state class of the parent class to extend the state - a single
+ * paintable can only have one shared state object.
+ * 
+ * Future versions of the shared state mechanism may also support custom data
+ * types as fields of a shared state class.
+ * 
+ * @since 7.0
+ */
+public class SharedState implements Serializable {
+    private Map<String, Object> state = new HashMap<String, Object>();
+
+    // TODO temporary until reflection based serialization is implemented
+    public Map<String, Object> getState() {
+        return state;
+    }
+
+    // TODO temporary until generator based deserialization is implemented
+    public void setState(Map<String, Object> stateMap) {
+        state = stateMap;
+    }
+}
index 0d25b484225ad34913e481a3b3793b3ad54a0668..168d6543f9069fd54fe50673202b4606231eef46 100644 (file)
@@ -63,6 +63,7 @@ import com.vaadin.terminal.WrappedRequest;
 import com.vaadin.terminal.WrappedResponse;
 import com.vaadin.terminal.gwt.client.ApplicationConnection;
 import com.vaadin.terminal.gwt.client.communication.MethodInvocation;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext;
 import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
 import com.vaadin.ui.AbstractComponent;
@@ -808,6 +809,37 @@ public abstract class AbstractCommunicationManager implements
             });
         }
 
+        if (paintables != null) {
+            // paint shared state before changes - for now, send the complete
+            // state of all modified and new components
+
+            // TODO problem: some components will only be created and registered
+            // below in the paint phase
+
+            JSONObject sharedStates = new JSONObject();
+            for (final Iterator<Paintable> i = paintables.iterator(); i
+                    .hasNext();) {
+                final Paintable p = i.next();
+                String paintableId = getPaintableId(p);
+                SharedState state = p.getState();
+                if (null != state) {
+                    // encode and send shared state
+                    try {
+                        JSONArray stateJsonArray = JsonCodec
+                                .encode(state, this);
+                        sharedStates.put(paintableId, stateJsonArray);
+                    } catch (JSONException e) {
+                        throw new PaintException(
+                                "Failed to serialize shared state for paintable "
+                                        + paintableId + ": " + e.getMessage());
+                    }
+                }
+            }
+            outWriter.print("\"state\":");
+            outWriter.append(sharedStates.toString());
+            outWriter.print(", "); // close changes
+        }
+
         outWriter.print("\"changes\":[");
 
         List<InvalidLayout> invalidComponentRelativeSizes = null;
@@ -877,9 +909,9 @@ public abstract class AbstractCommunicationManager implements
         }
 
         paintTarget.close();
-        outWriter.print("]"); // close changes
+        outWriter.print("]"); // close changes
 
-        outWriter.print("\"meta\" : {");
+        outWriter.print("\"meta\" : {");
         boolean metaOpen = false;
 
         if (repaintAll) {
index dfe4491400dbe911462459d75556185c38998961..4d7501e7393cfb949ac1a5ac894adf8fa7e9934c 100644 (file)
@@ -16,6 +16,7 @@ import com.vaadin.external.json.JSONException;
 import com.vaadin.external.json.JSONObject;
 import com.vaadin.terminal.Paintable;
 import com.vaadin.terminal.gwt.client.communication.JsonEncoder;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
 
 /**
  * Decoder for converting RPC parameters and other values from JSON in transfer
@@ -140,6 +141,11 @@ public class JsonCodec implements Serializable {
             // TODO as undefined type?
             return combineTypeAndValue(JsonEncoder.VTYPE_UNDEFINED,
                     JSONObject.NULL);
+        } else if (value instanceof SharedState) {
+            // TODO implement by encoding the bean
+            Map<String, Object> map = ((SharedState) value).getState();
+            return combineTypeAndValue(JsonEncoder.VTYPE_SHAREDSTATE,
+                    encodeMapContents(map, idMapper));
         } else if (value instanceof String[]) {
             String[] array = (String[]) value;
             JSONArray jsonArray = new JSONArray();
@@ -153,20 +159,11 @@ public class JsonCodec implements Serializable {
             return combineTypeAndValue(JsonEncoder.VTYPE_BOOLEAN, value);
         } else if (value instanceof Object[]) {
             Object[] array = (Object[]) value;
-            JSONArray jsonArray = new JSONArray();
-            for (int i = 0; i < array.length; ++i) {
-                // TODO handle object graph loops?
-                jsonArray.put(encode(array[i], idMapper));
-            }
+            JSONArray jsonArray = encodeArrayContents(array, idMapper);
             return combineTypeAndValue(JsonEncoder.VTYPE_ARRAY, jsonArray);
         } else if (value instanceof Map) {
             Map<String, Object> map = (Map<String, Object>) value;
-            JSONObject jsonMap = new JSONObject();
-            for (String mapKey : map.keySet()) {
-                // TODO handle object graph loops?
-                Object mapValue = map.get(mapKey);
-                jsonMap.put(mapKey, encode(mapValue, idMapper));
-            }
+            JSONObject jsonMap = encodeMapContents(map, idMapper);
             return combineTypeAndValue(JsonEncoder.VTYPE_MAP, jsonMap);
         } else if (value instanceof Paintable) {
             Paintable paintable = (Paintable) value;
@@ -178,6 +175,27 @@ public class JsonCodec implements Serializable {
         }
     }
 
+    private static JSONArray encodeArrayContents(Object[] array,
+            PaintableIdMapper idMapper) throws JSONException {
+        JSONArray jsonArray = new JSONArray();
+        for (int i = 0; i < array.length; ++i) {
+            // TODO handle object graph loops?
+            jsonArray.put(encode(array[i], idMapper));
+        }
+        return jsonArray;
+    }
+
+    private static JSONObject encodeMapContents(Map<String, Object> map,
+            PaintableIdMapper idMapper) throws JSONException {
+        JSONObject jsonMap = new JSONObject();
+        for (String mapKey : map.keySet()) {
+            // TODO handle object graph loops?
+            Object mapValue = map.get(mapKey);
+            jsonMap.put(mapKey, encode(mapValue, idMapper));
+        }
+        return jsonMap;
+    }
+
     private static JSONArray combineTypeAndValue(char type, Object value) {
         JSONArray outerArray = new JSONArray();
         outerArray.put(String.valueOf(type));
index 318bcd7ea2aceacba99d94d1d0c8669ca82dce9a..62be25f73bd8a2f60c018d2f350f3f42bed07b19 100644 (file)
@@ -29,6 +29,7 @@ import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.Terminal;
+import com.vaadin.terminal.gwt.client.ComponentState;
 import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
 import com.vaadin.terminal.gwt.server.RpcManager;
 import com.vaadin.terminal.gwt.server.RpcTarget;
@@ -163,6 +164,12 @@ public abstract class AbstractComponent implements Component, MethodEventSource
      */
     private Map<Class<?>, RpcManager> rpcManagerMap = new HashMap<Class<?>, RpcManager>();
 
+    /**
+     * Shared state object to be communicated from the server to the client when
+     * modified.
+     */
+    private ComponentState sharedState;
+
     /* Constructor */
 
     /**
@@ -853,6 +860,88 @@ public abstract class AbstractComponent implements Component, MethodEventSource
 
     }
 
+    /**
+     * Returns the shared state bean with information to be sent from the server
+     * to the client.
+     * 
+     * Subclasses should override this method and set any relevant fields of the
+     * state returned by super.getState().
+     * 
+     * @since 7.0
+     * 
+     * @return updated component shared state
+     */
+    public ComponentState getState() {
+        if (null == sharedState) {
+            sharedState = createState();
+        }
+        // basic state: caption, size, enabled, ...
+
+        if (!isVisible()) {
+            return null;
+        }
+
+        // TODO for now, this superclass always recreates the state from
+        // scratch, whereas subclasses should only modify it
+        Map<String, Object> state = new HashMap<String, Object>();
+
+        if (getHeight() >= 0
+                && (getHeightUnits() != Unit.PERCENTAGE || ComponentSizeValidator
+                        .parentCanDefineHeight(this))) {
+            state.put(ComponentState.STATE_HEIGHT, "" + getCSSHeight());
+        }
+
+        if (getWidth() >= 0
+                && (getWidthUnits() != Unit.PERCENTAGE || ComponentSizeValidator
+                        .parentCanDefineWidth(this))) {
+            state.put(ComponentState.STATE_WIDTH, "" + getCSSWidth());
+        }
+
+        if (styles != null && styles.size() > 0) {
+            state.put(ComponentState.STATE_STYLE, getStyle());
+        }
+        if (isReadOnly()) {
+            state.put(ComponentState.STATE_READONLY, true);
+        }
+
+        if (isImmediate()) {
+            state.put(ComponentState.STATE_IMMEDIATE, true);
+        }
+        if (!isEnabled()) {
+            state.put(ComponentState.STATE_DISABLED, true);
+        }
+        if (getCaption() != null) {
+            state.put(ComponentState.STATE_CAPTION, getCaption());
+        }
+        // TODO add icon (Resource)
+
+        if (getDescription() != null && getDescription().length() > 0) {
+            state.put(ComponentState.STATE_DESCRIPTION, getDescription());
+        }
+
+        sharedState.setState(state);
+
+        return sharedState;
+    }
+
+    /**
+     * Creates the shared state bean to be used in server to client
+     * communication.
+     * 
+     * Subclasses should implement this method and return a new instance of the
+     * correct state class.
+     * 
+     * All configuration of the values of the state should be performed in
+     * {@link #getState()}, not in {@link #createState()}.
+     * 
+     * @since 7.0
+     * 
+     * @return new shared state object
+     */
+    protected ComponentState createState() {
+        return new ComponentState();
+    }
+
     /* Documentation copied from interface */
     public void requestRepaint() {