From d432aa8fd8b4a357947bbb988f5bd6bc98e43f77 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Leif=20=C3=85strand?= Date: Thu, 1 Nov 2012 13:59:55 +0200 Subject: [PATCH] Properly detach removed connectors (#9815) Change-Id: I1b9a1a1bfbf384a8e5530ca8fddd1e0758d431f2 --- .../vaadin/client/ApplicationConnection.java | 84 +++++++++++++++++-- ...hyChangeForRemovedComponentContainers.html | 32 +++++++ ...hyChangeForRemovedComponentContainers.java | 61 ++++++++++++++ 3 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.html create mode 100644 uitest/src/com/vaadin/tests/components/HierarchyChangeForRemovedComponentContainers.java 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 maybeDetached = new HashSet(); + 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 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. 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 oldChildren = ccc + .getChildComponents(); + if (!oldChildren.isEmpty()) { + /* + * ComponentContainerConnector has a separate child + * component list that should also be cleared + */ + ccc.setChildComponents(Collections + . 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 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.components.HierarchyChangeForRemovedComponentContainers?restartApplication
clickvaadin=runcomvaadintestscomponentsHierarchyChangeForRemovedComponentContainers::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]/domChild[0]/domChild[0]
screenCapturetwo-buttons-no-NPE
+ + 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; + } + +} -- 2.39.5