From 741758d24ff4803ce674eacf79a3ecaaa8f26ed6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Tue, 20 Nov 2012 19:04:55 +0200 Subject: [PATCH] Add attach and detach listeners and events to ClientConnector (#8579, #10251) Change-Id: Ifda4b4a770fb7d330f0fe5d606f8415961a574a2 --- .../server/AbstractClientConnector.java | 31 ++- .../com/vaadin/server/ClientConnector.java | 86 ++++++++- .../com/vaadin/server/DragAndDropService.java | 16 ++ server/src/com/vaadin/ui/Component.java | 8 +- .../AttachDetachListeners.java | 180 ++++++++++++++++++ 5 files changed, 308 insertions(+), 13 deletions(-) create mode 100644 server/tests/src/com/vaadin/tests/server/clientconnector/AttachDetachListeners.java diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java index 45c6da7e77..8b5bd78d01 100644 --- a/server/src/com/vaadin/server/AbstractClientConnector.java +++ b/server/src/com/vaadin/server/AbstractClientConnector.java @@ -92,6 +92,30 @@ public abstract class AbstractClientConnector implements ClientConnector, private ErrorHandler errorHandler = null; + @Override + public void addAttachListener(AttachListener listener) { + addListener(AttachEvent.ATTACH_EVENT_IDENTIFIER, AttachEvent.class, + listener, AttachListener.attachMethod); + } + + @Override + public void removeAttachListener(AttachListener listener) { + removeListener(AttachEvent.ATTACH_EVENT_IDENTIFIER, AttachEvent.class, + listener); + } + + @Override + public void addDetachListener(DetachListener listener) { + addListener(DetachEvent.DETACH_EVENT_IDENTIFIER, DetachEvent.class, + listener, DetachListener.detachMethod); + } + + @Override + public void removeDetachListener(DetachListener listener) { + removeListener(DetachEvent.DETACH_EVENT_IDENTIFIER, DetachEvent.class, + listener); + } + /** * @deprecated As of 7.0.0, use {@link #markAsDirty()} instead */ @@ -568,10 +592,11 @@ public abstract class AbstractClientConnector implements ClientConnector, getUI().getConnectorTracker().registerConnector(this); + fireEvent(new AttachEvent(this)); + for (ClientConnector connector : getAllChildrenIterable(this)) { connector.attach(); } - } /** @@ -588,6 +613,8 @@ public abstract class AbstractClientConnector implements ClientConnector, connector.detach(); } + fireEvent(new DetachEvent(this)); + getUI().getConnectorTracker().unregisterConnector(this); } @@ -950,7 +977,6 @@ public abstract class AbstractClientConnector implements ClientConnector, if (eventRouter != null) { eventRouter.fireEvent(event); } - } /* @@ -971,5 +997,4 @@ public abstract class AbstractClientConnector implements ClientConnector, public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } - } diff --git a/server/src/com/vaadin/server/ClientConnector.java b/server/src/com/vaadin/server/ClientConnector.java index 440f282508..4a09158da5 100644 --- a/server/src/com/vaadin/server/ClientConnector.java +++ b/server/src/com/vaadin/server/ClientConnector.java @@ -16,7 +16,11 @@ package com.vaadin.server; import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Method; import java.util.Collection; +import java.util.EventListener; +import java.util.EventObject; import java.util.List; import org.json.JSONException; @@ -27,6 +31,7 @@ import com.vaadin.shared.communication.SharedState; import com.vaadin.ui.Component; import com.vaadin.ui.ComponentContainer; import com.vaadin.ui.UI; +import com.vaadin.util.ReflectTools; /** * Interface implemented by all connectors that are capable of communicating @@ -37,6 +42,81 @@ import com.vaadin.ui.UI; * */ public interface ClientConnector extends Connector { + + public static abstract class ConnectorEvent extends EventObject { + public ConnectorEvent(ClientConnector source) { + super(source); + } + + public ClientConnector getConnector() { + return (ClientConnector) getSource(); + } + } + + /** + * Event fired after a connector is attached to the application. + */ + public static class AttachEvent extends ConnectorEvent { + public static final String ATTACH_EVENT_IDENTIFIER = "clientConnectorAttach"; + + public AttachEvent(ClientConnector source) { + super(source); + } + } + + /** + * Interface for listening {@link DetachEvent connector detach events}. + * + */ + public static interface AttachListener extends EventListener, Serializable { + public static final Method attachMethod = ReflectTools.findMethod( + AttachListener.class, "attach", AttachEvent.class); + + /** + * Called when a AttachListener is notified of a AttachEvent. + * + * @param event + * The attach event that was fired. + */ + public void attach(AttachEvent event); + } + + /** + * Event fired before a connector is detached from the application. + */ + public static class DetachEvent extends ConnectorEvent { + public static final String DETACH_EVENT_IDENTIFIER = "clientConnectorDetach"; + + public DetachEvent(ClientConnector source) { + super(source); + } + } + + /** + * Interface for listening {@link DetachEvent connector detach events}. + * + */ + public static interface DetachListener extends EventListener, Serializable { + public static final Method detachMethod = ReflectTools.findMethod( + DetachListener.class, "detach", DetachEvent.class); + + /** + * Called when a DetachListener is notified of a DetachEvent. + * + * @param event + * The detach event that was fired. + */ + public void detach(DetachEvent event); + } + + public void addAttachListener(AttachListener listener); + + public void removeAttachListener(AttachListener listener); + + public void addDetachListener(DetachListener listener); + + public void removeDetachListener(DetachListener listener); + /** * An error event for connector related errors. Use {@link #getConnector()} * to find the connector where the error occurred or {@link #getComponent()} @@ -174,13 +254,13 @@ public interface ClientConnector extends Connector { public void attach(); /** - * Notifies the component that it is detached from the application. + * Notifies the connector that it is detached from the application. * *

* The caller of this method is {@link #setParent(ClientConnector)} if the * parent is in the application. When the parent is detached from the - * application it is its response to call {@link #detach()} for all the - * children and to detach itself from the terminal. + * application it is its responsibility to call {@link #detach()} for each + * of its children. *

*/ public void detach(); diff --git a/server/src/com/vaadin/server/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java index 0fa39fa0e8..529cce670e 100644 --- a/server/src/com/vaadin/server/DragAndDropService.java +++ b/server/src/com/vaadin/server/DragAndDropService.java @@ -358,4 +358,20 @@ public class DragAndDropService implements VariableOwner, ClientConnector { public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } + + @Override + public void addAttachListener(AttachListener listener) { + } + + @Override + public void removeAttachListener(AttachListener listener) { + } + + @Override + public void addDetachListener(DetachListener listener) { + } + + @Override + public void removeDetachListener(DetachListener listener) { + } } diff --git a/server/src/com/vaadin/ui/Component.java b/server/src/com/vaadin/ui/Component.java index 85140a8351..afde1cdbdf 100644 --- a/server/src/com/vaadin/ui/Component.java +++ b/server/src/com/vaadin/ui/Component.java @@ -18,7 +18,6 @@ package com.vaadin.ui; import java.io.Serializable; import java.util.EventListener; -import java.util.EventObject; import java.util.Locale; import com.vaadin.event.FieldEvents; @@ -580,11 +579,6 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * application, the {@code attach()} is called immediately from * {@link #setParent(Component)}. *

- *

- * This method must call {@link UI#componentAttached(Component)} to let the - * UI know that a new Component has been attached. - *

- * * *
      * public class AttachExample extends CustomComponent {
@@ -735,7 +729,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      * @see Component.Listener
      */
     @SuppressWarnings("serial")
-    public static class Event extends EventObject {
+    public static class Event extends ConnectorEvent {
 
         /**
          * Constructs a new event with the specified source component.
diff --git a/server/tests/src/com/vaadin/tests/server/clientconnector/AttachDetachListeners.java b/server/tests/src/com/vaadin/tests/server/clientconnector/AttachDetachListeners.java
new file mode 100644
index 0000000000..f5b940d97b
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/clientconnector/AttachDetachListeners.java
@@ -0,0 +1,180 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.tests.server.clientconnector;
+
+import org.easymock.EasyMock;
+import org.easymock.IArgumentMatcher;
+import org.easymock.IMocksControl;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.server.ClientConnector.AttachEvent;
+import com.vaadin.server.ClientConnector.AttachListener;
+import com.vaadin.server.ClientConnector.ConnectorEvent;
+import com.vaadin.server.ClientConnector.DetachEvent;
+import com.vaadin.server.ClientConnector.DetachListener;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.UI;
+
+public class AttachDetachListeners {
+
+    private IMocksControl control;
+
+    private VaadinSession session;
+    private UI ui;
+    private Layout content;
+    private Component component;
+
+    AttachListener attachListener;
+    DetachListener detachListener;
+
+    @Before
+    public void setUp() {
+        control = EasyMock.createStrictControl();
+
+        session = new VaadinSession(control.createMock(VaadinService.class));
+
+        ui = new UI() {
+            @Override
+            protected void init(VaadinRequest request) {
+            }
+        };
+        content = new CssLayout();
+        component = new Label();
+
+        attachListener = control.createMock(AttachListener.class);
+        detachListener = control.createMock(DetachListener.class);
+    }
+
+    @Test
+    public void attachListeners_setSessionLast() {
+        setupAttachListeners();
+
+        ui.setContent(content);
+        content.addComponent(component);
+        ui.setSession(session);
+
+        control.verify();
+    }
+
+    @Test
+    public void attachListeners_setSessionFirst() {
+        setupAttachListeners();
+
+        ui.setSession(session);
+        ui.setContent(content);
+        content.addComponent(component);
+
+        control.verify();
+    }
+
+    @Test
+    public void attachListeners_setSessionBetween() {
+        setupAttachListeners();
+
+        ui.setContent(content);
+        ui.setSession(session);
+        content.addComponent(component);
+
+        control.verify();
+    }
+
+    @Test
+    public void detachListeners_setSessionNull() {
+        setupDetachListeners();
+
+        ui.setContent(content);
+        content.addComponent(component);
+        ui.setSession(null);
+
+        control.verify();
+    }
+
+    @Test
+    public void detachListeners_removeComponent() {
+        setupDetachListeners();
+
+        ui.setContent(content);
+        content.addComponent(component);
+        content.removeAllComponents();
+        ui.setSession(null);
+
+        control.verify();
+    }
+
+    @Test
+    public void detachListeners_setContentNull() {
+        setupDetachListeners();
+
+        ui.setContent(content);
+        content.addComponent(component);
+        ui.setContent(null);
+        ui.setSession(null);
+
+        control.verify();
+    }
+
+    public static class EventEquals implements
+            IArgumentMatcher {
+
+        private E expected;
+
+        public EventEquals(E expected) {
+            this.expected = expected;
+        }
+
+        @Override
+        public void appendTo(StringBuffer buffer) {
+            buffer.append("EventEquals(");
+            buffer.append("expected " + expected.getClass().getSimpleName()
+                    + " with connector " + expected.getConnector());
+            buffer.append(")");
+        }
+
+        @Override
+        public boolean matches(Object argument) {
+            return expected.getClass().isInstance(argument)
+                    && ((ConnectorEvent) argument).getConnector() == expected
+                            .getConnector();
+        }
+    }
+
+    public static  E eventEquals(E expected) {
+        EasyMock.reportMatcher(new EventEquals(expected));
+        return null;
+    }
+
+    private void setupDetachListeners() {
+        detachListener.detach(eventEquals(new DetachEvent(component)));
+        detachListener.detach(eventEquals(new DetachEvent(content)));
+        detachListener.detach(eventEquals(new DetachEvent(ui)));
+
+        control.replay();
+
+        ui.addDetachListener(detachListener);
+        content.addDetachListener(detachListener);
+        component.addDetachListener(detachListener);
+
+        ui.setSession(session);
+    }
+
+    private void setupAttachListeners() {
+        attachListener.attach(eventEquals(new AttachEvent(ui)));
+        attachListener.attach(eventEquals(new AttachEvent(content)));
+        attachListener.attach(eventEquals(new AttachEvent(component)));
+
+        control.replay();
+
+        ui.addAttachListener(attachListener);
+        content.addAttachListener(attachListener);
+        component.addAttachListener(attachListener);
+    }
+}
-- 
2.39.5