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.
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.SystemError;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
/**
* <p>
}
+ public SharedState getState() {
+ // TODO implement: move relevant parts from paint() to getState()
+ return null;
+ }
+
/* Documented in super interface */
public void addListener(RepaintRequestListener listener) {
}
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;
/**
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.
*
import java.util.Iterator;
import java.util.List;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
+
/**
* Class for combining multiple error messages together.
*
}
}
+ public SharedState getState() {
+ // TODO implement: move relevant parts from paint() to getState()
+ return null;
+ }
+
/* Documented in super interface */
public void addListener(RepaintRequestListener listener) {
}
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
*/
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.
*/
import java.io.PrintWriter;
import java.io.StringWriter;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
/**
}
+ public SharedState getState() {
+ // TODO implement: move relevant parts from paint() to getState()
+ return null;
+ }
+
/**
* Returns the message of the error in HTML.
*
package com.vaadin.terminal;
+import com.vaadin.terminal.gwt.client.communication.SharedState;
import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
/**
target.endTag("error");
}
+ public SharedState getState() {
+ // TODO implement: move relevant parts from paint() to getState()
+ return null;
+ }
+
/* Documented in interface */
public void requestRepaintRequests() {
}
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;
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");
--- /dev/null
+/*
+@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";
+
+}
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
return '' + this[name];
}-*/;
+ native JavaScriptObject getJavaScriptObject(String name)
+ /*-{
+ return this[name];
+ }-*/;
+
}
\ No newline at end of file
* 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.
// 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;
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;
}
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()]);
}
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';
--- /dev/null
+/*
+@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;
+ }
+}
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;
});
}
+ 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;
}
paintTarget.close();
- outWriter.print("]"); // close changes
+ outWriter.print("], "); // close changes
- outWriter.print(", \"meta\" : {");
+ outWriter.print("\"meta\" : {");
boolean metaOpen = false;
if (repaintAll) {
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
// 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();
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;
}
}
+ 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));
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;
*/
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 */
/**
}
+ /**
+ * 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() {