From 88d1e517cb6ce95be6554984326256e1a1b83f4c Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 8 Mar 2012 17:56:20 +0200 Subject: [PATCH] #8500 Initial implementation for sending component hierarchy automatically to the client and calling a listener method (for the parent) when its child hierarchy has been updated. Minor cleanup of JSON handling at the same time. --- .../gwt/client/ApplicationConnection.java | 327 ++++++++++++------ .../gwt/client/ComponentConnector.java | 30 +- .../client/ComponentContainerConnector.java | 41 ++- .../ConnectorHierarchyChangedEvent.java | 66 ++++ src/com/vaadin/terminal/gwt/client/Util.java | 37 ++ .../client/ui/AbstractComponentConnector.java | 40 ++- .../AbstractComponentContainerConnector.java | 67 ++-- .../server/AbstractCommunicationManager.java | 79 ++++- src/com/vaadin/ui/Component.java | 13 + 9 files changed, 531 insertions(+), 169 deletions(-) create mode 100644 src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 35fc0a3058..8ac2f1f7b1 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -5,10 +5,13 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; @@ -996,121 +999,30 @@ public class ApplicationConnection { redirectTimer.schedule(1000 * sessionExpirationInterval); } - // three phases/loops: - // - changes: create paintables (if necessary) - // - state: set shared states - // - changes: call updateFromUIDL() for each paintable - - // Process changes - JsArray changes = json.getJSValueMapArray("changes"); - - ArrayList updatedComponentConnectors = new ArrayList(); componentCaptionSizeChanges.clear(); Duration updateDuration = new Duration(); - int length = changes.length(); - - VConsole.log(" * Creating connectors (if needed)"); - // create paintables if necessary - for (int i = 0; i < length; i++) { - try { - final UIDL change = changes.get(i).cast(); - final UIDL uidl = change.getChildUIDL(0); - Connector paintable = connectorMap.getConnector(uidl - .getId()); - if (null == paintable - && !uidl.getTag().equals( - configuration.getEncodedWindowTag())) { - // create, initialize and register the paintable - getConnector(uidl.getId(), uidl.getTag()); - } - } catch (final Throwable e) { - VConsole.error(e); - } - } - - VConsole.log(" * Updating connector states"); - // set states for all paintables mentioned in "state" - ValueMap states = json.getValueMap("state"); - JsArrayString keyArray = states.getKeyArray(); - for (int i = 0; i < keyArray.length(); i++) { - try { - String connectorId = keyArray.get(i); - Connector paintable = connectorMap - .getConnector(connectorId); - if (null != paintable) { - - JSONArray stateDataAndType = new JSONArray( - states.getJavaScriptObject(connectorId)); - - Object state = JsonDecoder.convertValue( - stateDataAndType, connectorMap); - - paintable.setState((SharedState) state); - } - } catch (final Throwable e) { - VConsole.error(e); - } - } - - VConsole.log(" * Passing UIDL to Vaadin 6 style connectors"); - // update paintables - for (int i = 0; i < length; i++) { - try { - final UIDL change = changes.get(i).cast(); - final UIDL uidl = change.getChildUIDL(0); - String connectorId = uidl.getId(); - - if (!connectorMap.hasConnector(connectorId) - && uidl.getTag().equals( - configuration.getEncodedWindowTag())) { - // First RootConnector update. Up until this - // point the connectorId for RootConnector has - // not been known - connectorMap.registerConnector(connectorId, view); - view.doInit(connectorId, ApplicationConnection.this); - } + // Ensure that all connectors that we are about to update exist + createConnectorsIfNeeded(json); - final ComponentConnector paintable = (ComponentConnector) connectorMap - .getConnector(connectorId); - if (paintable != null) { - paintable.updateFromUIDL(uidl, - ApplicationConnection.this); - updatedComponentConnectors.add(paintable); - } else { - VConsole.error("Received update for " - + uidl.getTag() - + ", but there is no such paintable (" - + connectorId + ") rendered."); + // Update states, do not fire events + updateConnectorState(json); - } + // Update hierarchy, do not fire events + Collection pendingHierarchyChangeEvents = updateConnectorHierarchy(json); - } catch (final Throwable e) { - VConsole.error(e); - } - } + // Fire state change events (TODO) + VConsole.log(" * Sending state change events"); - if (json.containsKey("rpc")) { - VConsole.log(" * Performing server to client RPC calls"); + // Fire hierarchy change events + sendHierarchyChangeEvents(pendingHierarchyChangeEvents); - JSONArray rpcCalls = new JSONArray( - json.getJavaScriptObject("rpc")); + // Update of legacy (UIDL) style connectors + updateVaadin6StyleConnectors(json); - int rpcLength = rpcCalls.size(); - for (int i = 0; i < rpcLength; i++) { - try { - JSONArray rpcCall = (JSONArray) rpcCalls.get(i); - MethodInvocation invocation = parseMethodInvocation(rpcCall); - VConsole.log("Server to client RPC call: " - + invocation); - rpcManager.applyInvocation(invocation, - getConnectorMap()); - } catch (final Throwable e) { - VConsole.error(e); - } - } - } + // Handle any RPC invocations done on the server side + handleRpcInvocations(json); if (json.containsKey("dd")) { // response contains data for drag and drop service @@ -1188,6 +1100,211 @@ public class ApplicationConnection { } + private void createConnectorsIfNeeded(ValueMap json) { + VConsole.log(" * Creating connectors (if needed)"); + + JsArray changes = json.getJSValueMapArray("changes"); + // FIXME: This should be based on shared state, not the old + // "changes" + int length = changes.length(); + for (int i = 0; i < length; i++) { + try { + final UIDL change = changes.get(i).cast(); + final UIDL uidl = change.getChildUIDL(0); + String connectorId = uidl.getId(); + Connector connector = connectorMap + .getConnector(connectorId); + if (connector != null) { + continue; + } + + // Connector does not exist so we must create it + if (!uidl.getTag().equals( + configuration.getEncodedWindowTag())) { + // create, initialize and register the paintable + getConnector(uidl.getId(), uidl.getTag()); + } else { + // First RootConnector update. Before this the + // RootConnector has been created but not + // initialized as the connector id has not been + // known + connectorMap.registerConnector(connectorId, view); + view.doInit(connectorId, ApplicationConnection.this); + } + } catch (final Throwable e) { + VConsole.error(e); + } + } + } + + private void updateVaadin6StyleConnectors(ValueMap json) { + JsArray changes = json.getJSValueMapArray("changes"); + int length = changes.length(); + + VConsole.log(" * Passing UIDL to Vaadin 6 style connectors"); + // update paintables + for (int i = 0; i < length; i++) { + try { + final UIDL change = changes.get(i).cast(); + final UIDL uidl = change.getChildUIDL(0); + String connectorId = uidl.getId(); + + final ComponentConnector paintable = (ComponentConnector) connectorMap + .getConnector(connectorId); + if (paintable != null) { + paintable.updateFromUIDL(uidl, + ApplicationConnection.this); + } else { + VConsole.error("Received update for " + + uidl.getTag() + + ", but there is no such paintable (" + + connectorId + ") rendered."); + + } + + } catch (final Throwable e) { + VConsole.error(e); + } + } + } + + private void sendHierarchyChangeEvents( + Collection pendingHierarchyChangeEvents) { + if (pendingHierarchyChangeEvents.isEmpty()) { + return; + } + + VConsole.log(" * Sending hierarchy change events"); + for (ConnectorHierarchyChangedEvent event : pendingHierarchyChangeEvents) { + event.getParent().connectorHierarchyChanged(event); + } + + } + + private void updateConnectorState(ValueMap json) { + VConsole.log(" * Updating connector states"); + // set states for all paintables mentioned in "state" + ValueMap states = json.getValueMap("state"); + JsArrayString keyArray = states.getKeyArray(); + for (int i = 0; i < keyArray.length(); i++) { + try { + String connectorId = keyArray.get(i); + Connector paintable = connectorMap + .getConnector(connectorId); + if (null != paintable) { + + JSONArray stateDataAndType = new JSONArray( + states.getJavaScriptObject(connectorId)); + + Object state = JsonDecoder.convertValue( + stateDataAndType, connectorMap); + + paintable.setState((SharedState) state); + } + } catch (final Throwable e) { + VConsole.error(e); + } + } + } + + /** + * Updates the connector hierarchy and returns a list of events that + * should be fired after update of the hierarchy and the state is + * done. + * + * @param json + * The JSON containing the hierarchy information + * @return A collection of events that should be fired when update + * of hierarchy and state is complete + */ + private Collection updateConnectorHierarchy( + ValueMap json) { + List events = new LinkedList(); + + VConsole.log(" * Updating connector hierarchy"); + ValueMap hierarchies = json.getValueMap("hierarchy"); + JsArrayString hierarchyKeys = hierarchies.getKeyArray(); + for (int i = 0; i < hierarchyKeys.length(); i++) { + try { + String connectorId = hierarchyKeys.get(i); + Connector connector = connectorMap + .getConnector(connectorId); + if (!(connector instanceof ComponentContainerConnector)) { + VConsole.error("Retrieved a hierarchy update for a connector (" + + connectorId + + ") that is not a ComponentContainerConnector"); + continue; + } + ComponentContainerConnector ccc = (ComponentContainerConnector) connector; + + JsArrayString childConnectorIds = hierarchies + .getJSStringArray(connectorId); + int childConnectorSize = childConnectorIds.length(); + + List newChildren = new ArrayList(); + for (int connectorIndex = 0; connectorIndex < childConnectorSize; connectorIndex++) { + String childConnectorId = childConnectorIds + .get(connectorIndex); + ComponentConnector childConnector = (ComponentConnector) connectorMap + .getConnector(childConnectorId); + newChildren.add(childConnector); + if (childConnector.getParent() != ccc) { + // Avoid extra calls to setParent + childConnector.setParent(ccc); + } + } + + // TODO This check should be done on the server side in + // the future so the hierarchy update is only sent when + // something actually has changed + Collection oldChildren = ccc + .getChildren(); + boolean actuallyChanged = !Util.collectionsEquals( + oldChildren, newChildren); + + if (!actuallyChanged) { + continue; + } + + // Fire change event if the hierarchy has changed + ConnectorHierarchyChangedEvent event = GWT + .create(ConnectorHierarchyChangedEvent.class); + event.setOldChildren(oldChildren); + event.setParent(ccc); + ccc.setChildren((Collection) newChildren); + events.add(event); + } catch (final Throwable e) { + VConsole.error(e); + } + } + return events; + + } + + private void handleRpcInvocations(ValueMap json) { + if (json.containsKey("rpc")) { + VConsole.log(" * Performing server to client RPC calls"); + + JSONArray rpcCalls = new JSONArray( + json.getJavaScriptObject("rpc")); + + int rpcLength = rpcCalls.size(); + for (int i = 0; i < rpcLength; i++) { + try { + JSONArray rpcCall = (JSONArray) rpcCalls.get(i); + MethodInvocation invocation = parseMethodInvocation(rpcCall); + VConsole.log("Server to client RPC call: " + + invocation); + rpcManager.applyInvocation(invocation, + getConnectorMap()); + } catch (final Throwable e) { + VConsole.error(e); + } + } + } + + } + }; ApplicationConfiguration.runWhenWidgetsLoaded(c); } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java index cb70287427..a083da0dff 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentConnector.java @@ -28,13 +28,6 @@ public interface ComponentConnector extends Connector { */ public Widget getWidget(); - /** - * Returns the parent {@link ComponentContainerConnector} - * - * @return - */ - public ComponentContainerConnector getParent(); - public LayoutManager getLayoutManager(); /** @@ -90,4 +83,27 @@ public interface ComponentConnector extends Connector { * @return the server side height definition */ public String getDeclaredHeight(); + + /** + * Returns the parent of this connector. Can be null for only the root + * connector. + * + * @return The parent of this connector, as set by + * {@link #setParent(ComponentContainerConnector)}. + */ + public ComponentContainerConnector getParent(); + + /** + * Sets the parent for this connector. This method should only be called by + * the framework to ensure that the connector hierarchy on the client side + * and the server side are in sync. + *

+ * Note that calling this method does not fire a + * {@link ConnectorHierarchyChangedEvent}. The event is fired only when the + * whole hierarchy has been updated. + * + * @param parent + * The new parent of the connector + */ + public void setParent(ComponentContainerConnector parent); } diff --git a/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java index c6b1e724f2..49e31e8690 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentContainerConnector.java @@ -9,7 +9,7 @@ import java.util.Collection; import com.google.gwt.user.client.ui.HasWidgets; /** - * An interface used by client-side paintables whose widget is a component + * An interface used by client-side connectors whose widget is a component * container (implements {@link HasWidgets}). */ public interface ComponentContainerConnector extends ComponentConnector { @@ -24,24 +24,51 @@ public interface ComponentContainerConnector extends ComponentConnector { * must provide service for it's childen to show those elements for them. *

* - * @param paintable + * @param connector * Child component for which service is requested. * @param uidl * UIDL of the child component. */ - void updateCaption(ComponentConnector paintable, UIDL uidl); + void updateCaption(ComponentConnector connector, UIDL uidl); /** - * Returns the children for this paintable. + * Returns the children for this connector. *

- * The children for this paintable are defined as all + * The children for this connector are defined as all * {@link ComponentConnector}s whose parent is this * {@link ComponentContainerConnector}. *

* - * @return A collection of children for this paintable. An empty collection - * if there are no children. + * @return A collection of children for this connector. An empty collection + * if there are no children. Never returns null. */ public Collection getChildren(); + /** + * Sets the children for this connector. This method should only be called + * by the framework to ensure that the connector hierarchy on the client + * side and the server side are in sync. + *

+ * Note that calling this method does not call + * {@link #connectorHierarchyChanged(ConnectorHierarchyChangedEvent)}. The + * event method is called only when the hierarchy has been updated for all + * connectors. + * + * @param children + * The new child connectors + */ + public void setChildren(Collection children); + + /** + * Called when the child connector hierarchy of this connector has changed. + * When this method is called the full hierarchy has been updated so + * {@link #getChildren()} returns the new child connectors of this + * connector. + * + * @param event + * An event containing additional information about how the + * hierarchy has changed. + */ + public void connectorHierarchyChanged(ConnectorHierarchyChangedEvent event); + } diff --git a/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java b/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java new file mode 100644 index 0000000000..43b61337ec --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ConnectorHierarchyChangedEvent.java @@ -0,0 +1,66 @@ +package com.vaadin.terminal.gwt.client; + +import java.util.Collection; + +/** + * Event for containing data related to a change in the {@link Connector} + * hierarchy. A {@link ConnectorHierarchyChangedEvent} is fired when an update + * from the server has been fully processed and all hierarchy updates have been + * completed. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + * + */ +public class ConnectorHierarchyChangedEvent { + Collection oldChildren; + private ComponentContainerConnector parent; + + public ConnectorHierarchyChangedEvent() { + } + + /** + * Returns a collection of the old children for the connector. This was the + * state before the update was received from the server. + * + * @return A collection of old child connectors. Never returns null. + */ + public Collection getOldChildren() { + return oldChildren; + } + + /** + * Sets the collection of the old children for the connector. + * + * @param oldChildren + * The old child connectors. Must not be null. + */ + public void setOldChildren(Collection oldChildren) { + this.oldChildren = oldChildren; + } + + /** + * Returns the {@link ComponentContainerConnector} for which this event + * occurred. + * + * @return The {@link ComponentContainerConnector} whose child collection + * has changed. Never returns null. + */ + public ComponentContainerConnector getParent() { + return parent; + } + + /** + * Sets the {@link ComponentContainerConnector} for which this event + * occurred. + * + * @param The + * {@link ComponentContainerConnector} whose child collection has + * changed. + */ + public void setParent(ComponentContainerConnector parent) { + this.parent = parent; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/Util.java b/src/com/vaadin/terminal/gwt/client/Util.java index 572a9bebb2..39962de907 100644 --- a/src/com/vaadin/terminal/gwt/client/Util.java +++ b/src/com/vaadin/terminal/gwt/client/Util.java @@ -6,6 +6,8 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import com.google.gwt.core.client.Scheduler; @@ -1094,4 +1096,39 @@ public class Util { boolean touchEvent = Util.isTouchEvent(event); return touchEvent || event.getButton() == Event.BUTTON_LEFT; } + + /** + * Performs a shallow comparison of the collections. + * + * @param collection1 The first collection + * @param collection2 The second collection + * @return true if the collections contain the same elements in the same + * order, false otherwise + */ + public static boolean collectionsEquals(Collection collection1, Collection collection2) { + if (collection1 == null) { + return collection2 == null; + } + if (collection2 == null) { + return false; + } + Iterator collection1Iterator = collection1.iterator(); + Iterator collection2Iterator = collection2.iterator(); + + while (collection1Iterator.hasNext()) { + if (!collection2Iterator.hasNext()) { + return false; + } + Object collection1Object = collection1Iterator.next(); + Object collection2Object = collection2Iterator.next(); + if (collection1Object != collection2Object) { + return false; + } + } + if (collection2Iterator.hasNext()) { + return false; + } + + return true; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java index 446d941399..f2d4f082d1 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentConnector.java @@ -24,6 +24,8 @@ import com.vaadin.terminal.gwt.client.communication.SharedState; public abstract class AbstractComponentConnector extends AbstractConnector implements ComponentConnector { + private ComponentContainerConnector parent; + // Generic UIDL parameter names, to be moved to shared state. // Attributes are here mainly if they apply to all paintable widgets or // affect captions - otherwise, they are in the relevant subclasses. @@ -110,23 +112,6 @@ public abstract class AbstractComponentConnector extends AbstractConnector return GWT.create(ComponentState.class); } - public ComponentContainerConnector getParent() { - // FIXME: Hierarchy should be set by framework instead of looked up here - ConnectorMap paintableMap = ConnectorMap.get(getConnection()); - - Widget w = getWidget(); - while (true) { - w = w.getParent(); - if (w == null) { - return null; - } - if (paintableMap.isConnector(w)) { - return (ComponentContainerConnector) paintableMap - .getConnector(w); - } - } - } - protected static boolean isRealUpdate(UIDL uidl) { return !isCachedUpdate(uidl) && !uidl.getBooleanAttribute("invisible") && !uidl.hasAttribute("deferred"); @@ -438,4 +423,25 @@ public abstract class AbstractComponentConnector extends AbstractConnector public LayoutManager getLayoutManager() { return LayoutManager.get(getConnection()); } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Connector#getParent() + */ + public ComponentContainerConnector getParent() { + return parent; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.Connector#setParent(com.vaadin.terminal + * .gwt.client.ComponentContainerConnector) + */ + public void setParent(ComponentContainerConnector parent) { + this.parent = parent; + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java index 12adb60fb1..db0cbc29a2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractComponentContainerConnector.java @@ -3,58 +3,61 @@ */ package com.vaadin.terminal.gwt.client.ui; -import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedList; -import com.google.gwt.user.client.ui.HasOneWidget; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ComponentConnector; import com.vaadin.terminal.gwt.client.ComponentContainerConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangedEvent; +import com.vaadin.terminal.gwt.client.Util; public abstract class AbstractComponentContainerConnector extends AbstractComponentConnector implements ComponentContainerConnector { + Collection children; + /** * Default constructor */ public AbstractComponentContainerConnector() { } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ComponentContainerConnector#getChildren() + */ public Collection getChildren() { - Collection children = new ArrayList(); - - addDescendantPaintables(getWidget(), children, - ConnectorMap.get(getConnection())); + if (children == null) { + return new LinkedList(); + } return children; } - private static void addDescendantPaintables(Widget widget, - Collection paintables, ConnectorMap paintableMap) { - // FIXME: Store hierarchy instead of doing lookup every time - - if (widget instanceof HasWidgets) { - for (Widget child : (HasWidgets) widget) { - addIfPaintable(child, paintables, paintableMap); - } - } else if (widget instanceof HasOneWidget) { - Widget child = ((HasOneWidget) widget).getWidget(); - addIfPaintable(child, paintables, paintableMap); - } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ComponentContainerConnector#setChildren + * (java.util.Collection) + */ + public void setChildren(Collection children) { + this.children = children; } - private static void addIfPaintable(Widget widget, - Collection paintables, ConnectorMap paintableMap) { - ComponentConnector paintable = paintableMap.getConnector(widget); - if (paintable != null) { - // If widget is a paintable, add it to the collection - paintables.add(paintable); - } else { - // Else keep looking for paintables inside the widget - addDescendantPaintables(widget, paintables, paintableMap); - } + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ComponentContainerConnector# + * connectorHierarchyChanged + * (com.vaadin.terminal.gwt.client.ConnectorHierarchyChangedEvent) + */ + public void connectorHierarchyChanged(ConnectorHierarchyChangedEvent event) { + //TODO Remove debug info + System.out.println("Hierarchy changed for " + Util.getSimpleName(this)); + System.out.println("* Old children: " + event.getOldChildren()); + System.out.println("* New children: " + getChildren()); } - } diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java index 749c9ece6f..553c6fe572 100644 --- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java +++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java @@ -26,6 +26,7 @@ import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; @@ -67,6 +68,8 @@ import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.AbstractField; import com.vaadin.ui.Component; +import com.vaadin.ui.ComponentContainer; +import com.vaadin.ui.Panel; import com.vaadin.ui.Root; /** @@ -806,8 +809,11 @@ public abstract class AbstractCommunicationManager implements windowCache); } + // TODO These seem unnecessary and could be removed/replaced by looping + // through paintQueue without removing paintables from it LinkedList stateQueue = new LinkedList(); LinkedList rpcPendingQueue = new LinkedList(); + LinkedList hierarchyPendingQueue = new LinkedList(); if (paintables != null) { @@ -819,6 +825,8 @@ public abstract class AbstractCommunicationManager implements final Paintable p = paintQueue.removeFirst(); // for now, all painted components may need a state refresh stateQueue.push(p); + // also a hierarchy update + hierarchyPendingQueue.push(p); // ... and RPC calls to be sent rpcPendingQueue.push(p); @@ -870,7 +878,7 @@ public abstract class AbstractCommunicationManager implements outWriter.print("], "); // close changes if (!stateQueue.isEmpty()) { - // paint shared state + // send shared state to client // for now, send the complete state of all modified and new // components @@ -903,6 +911,45 @@ public abstract class AbstractCommunicationManager implements outWriter.append(sharedStates.toString()); outWriter.print(", "); // close states } + if (!hierarchyPendingQueue.isEmpty()) { + // Send update hierarchy information to the client. + + // This could be optimized aswell to send only info if hierarchy has + // actually changed. Much like with the shared state. Note though + // that an empty hierarchy is information aswell (e.g. change from 1 + // child to 0 children) + + outWriter.print("\"hierarchy\":"); + + JSONObject hierarchyInfo = new JSONObject(); + for (Paintable p : hierarchyPendingQueue) { + if (p instanceof ComponentContainer) { + ComponentContainer cc = (ComponentContainer) p; + String connectorId = paintableIdMap.get(cc); + JSONArray children = new JSONArray(); + + Iterator iterator = getChildComponentIterator(cc); + while (iterator.hasNext()) { + Component child = iterator.next(); + if (child.getState().isVisible()) { + String childConnectorId = paintableIdMap.get(child); + children.put(childConnectorId); + } + } + try { + hierarchyInfo.put(connectorId, children); + } catch (JSONException e) { + throw new PaintException( + "Failed to send hierarchy information about " + + connectorId + " to the client: " + + e.getMessage()); + } + } + } + outWriter.append(hierarchyInfo.toString()); + outWriter.print(", "); // close states + + } // send server to client RPC calls for components in the root, in call // order @@ -1093,6 +1140,34 @@ public abstract class AbstractCommunicationManager implements } } + private static class NullIterator implements Iterator { + + public boolean hasNext() { + return false; + } + + public E next() { + return null; + } + + public void remove() { + } + + } + + private Iterator getChildComponentIterator(ComponentContainer cc) { + if (cc instanceof Panel) { + // This is so wrong.. (#2924) + if (((Panel) cc).getContent() == null) { + return new NullIterator(); + } else { + return Collections.singleton( + (Component) ((Panel) cc).getContent()).iterator(); + } + } + return cc.getComponentIterator(); + } + /** * Collects all pending RPC calls from listed {@link Paintable}s and clears * their RPC queues. @@ -2197,6 +2272,8 @@ public abstract class AbstractCommunicationManager implements writeUidlResponse(true, pWriter, root, false); pWriter.print("}"); String initialUIDL = sWriter.toString(); + System.out.println("Initial UIDL:"); + System.out.println(initialUIDL); return initialUIDL; } diff --git a/src/com/vaadin/ui/Component.java b/src/com/vaadin/ui/Component.java index 7de94f6696..0354bccd1f 100644 --- a/src/com/vaadin/ui/Component.java +++ b/src/com/vaadin/ui/Component.java @@ -17,6 +17,7 @@ import com.vaadin.terminal.Paintable; import com.vaadin.terminal.Resource; import com.vaadin.terminal.Sizeable; import com.vaadin.terminal.VariableOwner; +import com.vaadin.terminal.gwt.client.ComponentState; /** * {@code Component} is the top-level interface that is and must be implemented @@ -684,6 +685,18 @@ public interface Component extends Paintable, VariableOwner, Sizeable, */ public Locale getLocale(); + /** + * Returns the current shared state bean for the component. The state (or + * changes to it) is communicated from the server to the client. + * + * Subclasses can use a more specific return type for this method. + * + * @return The state object for the component + * + * @since 7.0 + */ + public ComponentState getState(); + /** * The child components of the component must call this method when they * need repainting. The call must be made even in the case in which the -- 2.39.5