]> source.dussan.org Git - vaadin-framework.git/commitdiff
Added possibility to add listener for connectorMarkedDirty (#10773)
authorcaalador <mikael.grankvist@gmail.com>
Wed, 4 Apr 2018 12:18:57 +0000 (15:18 +0300)
committerIlia Motornyi <elmot@vaadin.com>
Wed, 4 Apr 2018 12:18:57 +0000 (15:18 +0300)
server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java [new file with mode: 0644]
server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/ConnectorTracker.java
server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java [new file with mode: 0644]

diff --git a/server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java b/server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java
new file mode 100644 (file)
index 0000000..7a18cbd
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2018 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.event;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.ui.UI;
+
+/**
+ * Event which is fired for all registered MarkDirtyListeners when a
+ * connector is marked as dirty.
+ *
+ * @since 8.4
+ */
+public class MarkedAsDirtyConnectorEvent extends ConnectorEvent {
+
+    private final UI ui;
+
+    public MarkedAsDirtyConnectorEvent(ClientConnector source, UI ui) {
+        super(source);
+        this.ui = ui;
+    }
+
+    /**
+     * Get the UI for which the connector event was fired
+     *
+     * @return target ui for event
+     */
+    public UI getUi() {
+        return ui;
+    }
+}
diff --git a/server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java b/server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java
new file mode 100644 (file)
index 0000000..f973de3
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2018 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.event;
+
+/**
+ * An interface used for listening to marked as dirty events.
+ *
+ * @since 8.4
+ */
+@FunctionalInterface
+public interface MarkedAsDirtyListener extends ConnectorEventListener {
+
+    /**
+     * Method called when a client connector has been marked as dirty.
+     *
+     * @param event
+     *         marked as dirty connector event object
+     */
+    void connectorMarkedAsDirty(MarkedAsDirtyConnectorEvent event);
+}
index 10ba15b6a6e9f4d292ee663ed672aa762beb8e5f..0c331ae8eec3115a2e3fa1e2cbfba979c99fcdd5 100644 (file)
@@ -25,12 +25,15 @@ 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;
 import java.util.UUID;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.vaadin.event.MarkedAsDirtyConnectorEvent;
+import com.vaadin.event.MarkedAsDirtyListener;
 import com.vaadin.server.AbstractClientConnector;
 import com.vaadin.server.ClientConnector;
 import com.vaadin.server.DragAndDropService;
@@ -40,6 +43,7 @@ import com.vaadin.server.StreamVariable;
 import com.vaadin.server.VaadinRequest;
 import com.vaadin.server.VaadinService;
 import com.vaadin.server.communication.ConnectorHierarchyWriter;
+import com.vaadin.shared.Registration;
 
 import elemental.json.Json;
 import elemental.json.JsonException;
@@ -71,6 +75,8 @@ public class ConnectorTracker implements Serializable {
     private final Set<ClientConnector> dirtyConnectors = new HashSet<>();
     private final Set<ClientConnector> uninitializedConnectors = new HashSet<>();
 
+    private List<MarkedAsDirtyListener> markedDirtyListeners = new ArrayList<>(0);
+
     /**
      * Connectors that have been unregistered and should be cleaned up the next
      * time {@link #cleanConnectorMap(boolean)} is invoked unless they have been
@@ -502,7 +508,8 @@ public class ConnectorTracker implements Serializable {
     }
 
     /**
-     * Mark the connector as dirty. This should not be done while the response
+     * Mark the connector as dirty and notifies any marked as dirty listeners.
+     * This should not be done while the response
      * is being written.
      *
      * @see #getDirtyConnectors()
@@ -518,12 +525,16 @@ public class ConnectorTracker implements Serializable {
         }
 
         if (getLogger().isLoggable(Level.FINE)) {
-            if (!dirtyConnectors.contains(connector)) {
+            if (!isDirty(connector)) {
                 getLogger().log(Level.FINE, "{0} is now dirty",
                         getConnectorAndParentInfo(connector));
             }
         }
 
+        if(!isDirty(connector)) {
+            notifyMarkedAsDirtyListeners(connector);
+        }
+
         dirtyConnectors.add(connector);
     }
 
@@ -899,4 +910,36 @@ public class ConnectorTracker implements Serializable {
     public int getCurrentSyncId() {
         return currentSyncId;
     }
+
+    /**
+     * Add a marked as dirty listener that will be called when a client
+     * connector is marked as dirty.
+     *
+     * @param listener
+     *         listener to add
+     * @since 8.4
+     * @return registration for removing listener registration
+     */
+    public Registration addMarkedAsDirtyListener(
+            MarkedAsDirtyListener listener) {
+        markedDirtyListeners.add(listener);
+        return () -> markedDirtyListeners.remove(listener);
+    }
+
+    /**
+     * Notify all registered MarkedAsDirtyListeners the given client connector
+     * has been marked as dirty.
+     *
+     * @param connector
+     *         client connector marked as dirty
+     * @since 8.4
+     */
+    public void notifyMarkedAsDirtyListeners(ClientConnector connector) {
+        MarkedAsDirtyConnectorEvent event = new MarkedAsDirtyConnectorEvent(
+                connector, uI);
+        new ArrayList<>(markedDirtyListeners).forEach(listener -> {
+            listener.connectorMarkedAsDirty(event);
+        });
+    }
+
 }
diff --git a/server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java b/server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java
new file mode 100644 (file)
index 0000000..461ab74
--- /dev/null
@@ -0,0 +1,155 @@
+package com.vaadin.tests.event;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.event.MarkedAsDirtyConnectorEvent;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.tests.util.MockUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.ComponentTest;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.UI;
+
+/**
+ * Test for mark as dirty listener functionality.
+ */
+public class MarkAsDirtyListenerTest {
+
+    @Test
+    public void fire_event_when_ui_marked_dirty() {
+        UI ui = new MockUI();
+
+        AtomicReference<MarkedAsDirtyConnectorEvent> events = new AtomicReference<>();
+        ui.getConnectorTracker().addMarkedAsDirtyListener(event -> Assert
+                .assertTrue("No reference should have been registered",
+                        events.compareAndSet(null, event)));
+        // UI is marked dirty on creation and when adding a listener
+        ComponentTest.syncToClient(ui);
+
+        ui.getConnectorTracker().markDirty(ui);
+
+        Assert.assertNotNull("Mark as dirty event should have fired",
+                events.get());
+        Assert.assertEquals("Event contains wrong ui", ui,
+                events.get().getUi());
+        Assert.assertEquals("Found wrong connector in event", ui,
+                events.get().getConnector());
+    }
+
+    @Test
+    public void fire_event_for_setContent() {
+        List<MarkedAsDirtyConnectorEvent> events = new ArrayList<>();
+        UI ui = new MockUI() {{
+            getConnectorTracker().addMarkedAsDirtyListener(event -> events.add(event));
+        }};
+        ComponentTest.syncToClient(ui);
+
+        Button button = new Button("Button");
+        ui.setContent(button);
+
+        Assert.assertEquals("Mark as dirty events should have fired", 2,
+                events.size());
+        Assert.assertEquals("Expected button to inform first for creation",
+                button, events.get(0).getConnector());
+        Assert.assertEquals("Expected UI marked as dirty for setContent", ui,
+                events.get(1).getConnector());
+    }
+
+    @Test
+    public void fire_event_for_component_stateChange() {
+        UI ui = new MockUI();
+        Button button = new Button("empty");
+        ui.setContent(button);
+        ComponentTest.syncToClient(button);
+
+        AtomicReference<MarkedAsDirtyConnectorEvent> events = new AtomicReference<>();
+        ui.getConnectorTracker().addMarkedAsDirtyListener(event -> Assert
+                .assertTrue("No reference should have been registered",
+                        events.compareAndSet(null, event)));
+
+        button.setIconAlternateText("alternate");
+
+        Assert.assertNotNull("Mark as dirty event should have fired",
+                events.get());
+        Assert.assertEquals("Event contains wrong ui", ui,
+                events.get().getUi());
+        Assert.assertEquals("Found wrong connector in event", button,
+                events.get().getConnector());
+    }
+
+    @Test
+    public void fire_events_for_each_component() {
+        List<MarkedAsDirtyConnectorEvent> events = new ArrayList<>();
+        UI ui = new MockUI() {{
+            getConnectorTracker().addMarkedAsDirtyListener(event -> events.add(event));
+        }};
+
+        HorizontalLayout layout = new HorizontalLayout();
+        // UI initially marked as dirty so should not show as event.
+        ui.setContent(layout);
+        TextField field = new TextField("Name");
+        Button button = new Button("say hello");
+        layout.addComponents(field, button);
+
+        Assert.assertFalse("Mark as dirty event should have fired",
+                events.isEmpty());
+        Assert.assertEquals("Unexpected amount of connector events", 3,
+                events.size());
+
+        Set<ClientConnector> connectors = events.stream()
+                .map(MarkedAsDirtyConnectorEvent::getConnector)
+                .collect(Collectors.toSet());
+
+        Assert.assertTrue(
+                "HorizontalLayout should have fired an markedAsDirty event",
+                connectors.contains(layout));
+        Assert.assertTrue("TextField should have fired an markedAsDirty event",
+                connectors.contains(field));
+        Assert.assertTrue("Button should have fired an markedAsDirty event",
+                connectors.contains(button));
+    }
+
+    @Test
+    public void event_should_only_fire_once_for_an_connector_per_roundtrip() {
+        UI ui = new MockUI();
+        Button button = new Button("empty");
+        ui.setContent(button);
+        ComponentTest.syncToClient(button);
+
+        AtomicReference<MarkedAsDirtyConnectorEvent> events = new AtomicReference<>();
+        ui.getConnectorTracker().addMarkedAsDirtyListener(event -> Assert
+                .assertTrue("Only one event should have registered",
+                        events.compareAndSet(null, event)));
+
+        button.setIconAlternateText("alternate");
+        button.setCaption("Update");
+        button.setDisableOnClick(true);
+
+        Assert.assertNotNull("Mark as dirty event should have fired",
+                events.get());
+        Assert.assertEquals("Event contains wrong ui", ui,
+                events.get().getUi());
+        Assert.assertEquals("Found wrong connector in event", button,
+                events.get().getConnector());
+
+        events.set(null);
+        ComponentTest.syncToClient(button);
+
+        button.setCaption("new caption");
+
+        Assert.assertNotNull("Mark as dirty event should have fired",
+                events.get());
+        Assert.assertEquals("Found wrong connector in event", button,
+                events.get().getConnector());
+    }
+
+}