summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeif Åstrand <leif@vaadin.com>2012-11-01 13:59:55 +0200
committerLeif Åstrand <leif@vaadin.com>2012-11-01 13:59:55 +0200
commitd432aa8fd8b4a357947bbb988f5bd6bc98e43f77 (patch)
tree7f0d3fb2e31a3d064625d493c8b6eb5ab0081f3f
parent008b582c66166b1a2e7ada4510e3a3d8be433a10 (diff)
downloadvaadin-framework-d432aa8fd8b4a357947bbb988f5bd6bc98e43f77.tar.gz
vaadin-framework-d432aa8fd8b4a357947bbb988f5bd6bc98e43f77.zip
Properly detach removed connectors (#9815)
Change-Id: I1b9a1a1bfbf384a8e5530ca8fddd1e0758d431f2
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java84
-rw-r--r--uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.html32
-rw-r--r--uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.java61
3 files changed, 171 insertions, 6 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index b43ed6c9fa..e227ef64e4 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -1799,6 +1799,8 @@ public class ApplicationConnection {
return events;
}
+ HashSet<ServerConnector> maybeDetached = new HashSet<ServerConnector>();
+
ValueMap hierarchies = json.getValueMap("hierarchy");
JsArrayString hierarchyKeys = hierarchies.getKeyArray();
for (int i = 0; i < hierarchyKeys.length(); i++) {
@@ -1837,8 +1839,10 @@ public class ApplicationConnection {
+ " is not a ComponentConnector nor an AbstractExtensionConnector");
}
if (childConnector.getParent() != parentConnector) {
- // Avoid extra calls to setParent
childConnector.setParent(parentConnector);
+ // Not detached even if previously removed from
+ // parent
+ maybeDetached.remove(childConnector);
}
}
@@ -1877,27 +1881,95 @@ public class ApplicationConnection {
parentConnector.setChildren(newChildren);
- // Remove parent for children that are no longer
- // attached to this (avoid updating children if they
- // have already been assigned to a new parent)
+ /*
+ * Find children removed from this parent and mark for
+ * removal unless they are already attached to some
+ * other parent.
+ */
for (ServerConnector oldChild : oldChildren) {
if (oldChild.getParent() != parentConnector) {
+ // Ignore if moved to some other connector
continue;
}
- // TODO This could probably be optimized
if (!newChildren.contains(oldChild)) {
- oldChild.setParent(null);
+ /*
+ * Consider child detached for now, will be
+ * cleared if it is later on added to some other
+ * parent.
+ */
+ maybeDetached.add(oldChild);
}
}
} catch (final Throwable e) {
VConsole.error(e);
}
}
+
+ /*
+ * Connector is in maybeDetached at this point if it has been
+ * removed from its parent but not added to any other parent
+ */
+ for (ServerConnector removed : maybeDetached) {
+ recursivelyDetach(removed, events);
+ }
+
return events;
}
+ private void recursivelyDetach(ServerConnector connector,
+ List<ConnectorHierarchyChangeEvent> events) {
+ /*
+ * Recursively detach children to make sure they get
+ * setParent(null) and hierarchy change events as needed.
+ */
+ for (ServerConnector child : connector.getChildren()) {
+ /*
+ * Server doesn't send updated child data for removed
+ * connectors -> ignore child that still seems to be a child
+ * of this connector although it has been moved to some part
+ * of the hierarchy that is not detached.
+ */
+ if (child.getParent() != connector) {
+ continue;
+ }
+ recursivelyDetach(child, events);
+ }
+
+ /*
+ * Clear child list and parent
+ */
+ connector
+ .setChildren(Collections.<ServerConnector> emptyList());
+ connector.setParent(null);
+
+ /*
+ * Create an artificial hierarchy event for containers to give
+ * it a chance to clean up after its children if it has any
+ */
+ if (connector instanceof ComponentContainerConnector) {
+ ComponentContainerConnector ccc = (ComponentContainerConnector) connector;
+ List<ComponentConnector> oldChildren = ccc
+ .getChildComponents();
+ if (!oldChildren.isEmpty()) {
+ /*
+ * ComponentContainerConnector has a separate child
+ * component list that should also be cleared
+ */
+ ccc.setChildComponents(Collections
+ .<ComponentConnector> emptyList());
+
+ // Create event and add it to the list of pending events
+ ConnectorHierarchyChangeEvent event = GWT
+ .create(ConnectorHierarchyChangeEvent.class);
+ event.setConnector(connector);
+ event.setOldChildren(oldChildren);
+ events.add(event);
+ }
+ }
+ }
+
private void handleRpcInvocations(ValueMap json) {
if (json.containsKey("rpc")) {
VConsole.log(" * Performing server to client RPC calls");
diff --git a/uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.html b/uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.html
new file mode 100644
index 0000000000..49b02d3f01
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost:8888/" />
+<title>New Test</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">New Test</td></tr>
+</thead><tbody>
+<tr>
+ <td>open</td>
+ <td>/run/com.vaadin.tests.components.HierarchyChangeForRemovedComponentContainers?restartApplication</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentsHierarchyChangeForRemovedComponentContainers::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>two-buttons-no-NPE</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.java b/uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.java
new file mode 100644
index 0000000000..db6ff9f537
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.java
@@ -0,0 +1,61 @@
+package com.vaadin.tests.components;
+
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.VerticalLayout;
+
+public class HierarchyChangeForRemovedComponentContainers extends TestBase {
+
+
+ private HorizontalLayout mainContent;
+ private VerticalLayout lo2;
+
+ @Override
+ protected void setup() {
+
+ mainContent = new HorizontalLayout();
+ mainContent.setSizeFull();
+
+ lo2 = new VerticalLayout();
+ Button button1 = new Button("asdasd1");
+ button1.setHeight("90%");
+ Button button2 = new Button("asdasd2");
+ button2.setHeight("90%");
+ lo2.addComponent(button1);
+ lo2.addComponent(button2);
+
+ compose();
+
+ addComponent(new Button("Replace layout with button",
+ new Button.ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ compose2();
+ }
+ }));
+ }
+
+ private void compose() {
+ getLayout().removeAllComponents();
+ getLayout().addComponent(mainContent);
+ mainContent.addComponent(lo2);
+ System.out.println("composed");
+ }
+
+ private void compose2() {
+ getLayout().removeAllComponents();
+ getLayout().addComponent(lo2);
+ }
+
+ @Override
+ protected String getDescription() {
+ return "HierarchyChange events should be triggered for removed layouts";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 9815;
+ }
+
+}