From 906c9ac72dab6126d03ae98842ac79427f2bf16e Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 30 May 2016 16:11:37 +0300 Subject: [PATCH] Add support for event listeners in template elements --- .../impl/TemplateElementStateProvider.java | 35 ++++++++++- .../nodefeature/TemplateOverridesMap.java | 5 +- .../communication/ServerRpcHandler.java | 2 +- .../dom/TemplateElementStateProviderTest.java | 15 +++++ .../communication/ServerRpcHandlerTest.java | 34 +++++++++++ .../test/java/com/vaadin/ui/TemplateTest.java | 30 ++++++++++ .../uitest/ui/BasicTemplateView.java | 7 +-- .../ui/TemplateComponentMappingView.java | 59 +++++++++++++++++++ .../uitest/ui/BasicTemplateView.html | 2 +- .../ui/TemplateComponentMappingView.html | 17 ++++++ .../uitest/ui/TemplateComponentMappingIT.java | 48 +++++++++++++++ 11 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.java create mode 100644 hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.html create mode 100644 hummingbird-tests/test-root-context/src/test/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingIT.java diff --git a/hummingbird-server/src/main/java/com/vaadin/hummingbird/dom/impl/TemplateElementStateProvider.java b/hummingbird-server/src/main/java/com/vaadin/hummingbird/dom/impl/TemplateElementStateProvider.java index 0145707ab35..978f6672c50 100644 --- a/hummingbird-server/src/main/java/com/vaadin/hummingbird/dom/impl/TemplateElementStateProvider.java +++ b/hummingbird-server/src/main/java/com/vaadin/hummingbird/dom/impl/TemplateElementStateProvider.java @@ -34,8 +34,12 @@ import com.vaadin.hummingbird.dom.EventRegistrationHandle; import com.vaadin.hummingbird.dom.Style; import com.vaadin.hummingbird.nodefeature.ComponentMapping; +import com.vaadin.hummingbird.nodefeature.ElementChildrenList; +import com.vaadin.hummingbird.nodefeature.ElementListenerMap; +import com.vaadin.hummingbird.nodefeature.ElementPropertyMap; import com.vaadin.hummingbird.nodefeature.ModelMap; import com.vaadin.hummingbird.nodefeature.NodeFeature; +import com.vaadin.hummingbird.nodefeature.OverrideElementData; import com.vaadin.hummingbird.nodefeature.ParentGeneratorHolder; import com.vaadin.hummingbird.nodefeature.TemplateEventHandlerNames; import com.vaadin.hummingbird.nodefeature.TemplateMap; @@ -131,6 +135,13 @@ public int size() { .concat(Stream.of(requiredFeatures), Stream.of(rootOnlyFeatures)) .toArray(Class[]::new); + @SuppressWarnings("unchecked") + private static Class[] overrideNodeFeatures = Stream + .of(OverrideElementData.class, ElementChildrenList.class, + ParentGeneratorHolder.class, ComponentMapping.class, + ElementPropertyMap.class, ElementListenerMap.class) + .toArray(Class[]::new); + private static final String CANT_MODIFY_MESSAGE = "Can't modify element defined in a template"; private ElementTemplateNode templateNode; @@ -145,6 +156,15 @@ public TemplateElementStateProvider(ElementTemplateNode templateNode) { this.templateNode = templateNode; } + /** + * Gets the template node for this state provider. + * + * @return the template node + */ + public ElementTemplateNode getTemplateNode() { + return templateNode; + } + @Override public boolean supports(StateNode node) { return Stream.of(requiredFeatures).allMatch(node::hasFeature); @@ -302,7 +322,10 @@ private void modifyOverrideNode(StateNode node, public EventRegistrationHandle addEventListener(StateNode node, String eventType, DomEventListener listener, String[] eventDataExpressions) { - throw new UnsupportedOperationException(CANT_MODIFY_MESSAGE); + ElementListenerMap listeners = getOrCreateOverrideNode(node) + .getFeature(ElementListenerMap.class); + + return listeners.add(eventType, listener, eventDataExpressions); } @Override @@ -453,6 +476,16 @@ public static StateNode createSubModelNode() { return new StateNode(requiredFeatures); } + /** + * Creates a new state node with all features needed for a state node use as + * an override node. + * + * @return a new state node, not null + */ + public static StateNode createOverrideNode() { + return new StateNode(overrideNodeFeatures); + } + private void checkModifiableProperty(String name) { if (templateNode.getPropertyBinding(name).isPresent()) { throw new IllegalArgumentException(String.format( diff --git a/hummingbird-server/src/main/java/com/vaadin/hummingbird/nodefeature/TemplateOverridesMap.java b/hummingbird-server/src/main/java/com/vaadin/hummingbird/nodefeature/TemplateOverridesMap.java index 772d342e86b..2ff5092a69f 100644 --- a/hummingbird-server/src/main/java/com/vaadin/hummingbird/nodefeature/TemplateOverridesMap.java +++ b/hummingbird-server/src/main/java/com/vaadin/hummingbird/nodefeature/TemplateOverridesMap.java @@ -16,6 +16,7 @@ package com.vaadin.hummingbird.nodefeature; import com.vaadin.hummingbird.StateNode; +import com.vaadin.hummingbird.dom.impl.TemplateElementStateProvider; import com.vaadin.hummingbird.template.TemplateNode; /** @@ -56,9 +57,7 @@ public StateNode get(TemplateNode templateNode, boolean create) { StateNode overrideNode = (StateNode) get(key); if (overrideNode == null && create) { - overrideNode = new StateNode(OverrideElementData.class, - ElementChildrenList.class, ParentGeneratorHolder.class, - ComponentMapping.class, ElementPropertyMap.class); + overrideNode = TemplateElementStateProvider.createOverrideNode(); overrideNode.getFeature(OverrideElementData.class) .setTemplateNode(templateNode); diff --git a/hummingbird-server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java b/hummingbird-server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java index 714ffbc0e88..1feb3a4761c 100644 --- a/hummingbird-server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java +++ b/hummingbird-server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java @@ -503,7 +503,7 @@ private void checkWidgetsetVersion(String widgetsetVersion) { * JSON containing all information needed to execute all * requested RPC calls. */ - private void handleInvocations(UI ui, int lastSyncIdSeenByClient, + void handleInvocations(UI ui, int lastSyncIdSeenByClient, JsonArray invocationsData) { for (int i = 0; i < invocationsData.length(); i++) { diff --git a/hummingbird-server/src/test/java/com/vaadin/hummingbird/dom/TemplateElementStateProviderTest.java b/hummingbird-server/src/test/java/com/vaadin/hummingbird/dom/TemplateElementStateProviderTest.java index 349d52e6d85..27968cc3d8c 100644 --- a/hummingbird-server/src/test/java/com/vaadin/hummingbird/dom/TemplateElementStateProviderTest.java +++ b/hummingbird-server/src/test/java/com/vaadin/hummingbird/dom/TemplateElementStateProviderTest.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; @@ -711,4 +712,18 @@ public static Element createElement(TemplateNode templateNode) { return Element.get(stateNode); } + + public static Optional getOverrideNode(Element element) { + StateNode node = element.getNode(); + if (!node.hasFeature(TemplateOverridesMap.class)) { + return Optional.empty(); + } else { + ElementStateProvider stateProvider = element.getStateProvider(); + assert stateProvider instanceof TemplateElementStateProvider; + return Optional.of(node.getFeature(TemplateOverridesMap.class) + .get(((TemplateElementStateProvider) stateProvider) + .getTemplateNode(), false)); + } + } + } diff --git a/hummingbird-server/src/test/java/com/vaadin/server/communication/ServerRpcHandlerTest.java b/hummingbird-server/src/test/java/com/vaadin/server/communication/ServerRpcHandlerTest.java index d9f6c5fff98..5753528710b 100644 --- a/hummingbird-server/src/test/java/com/vaadin/server/communication/ServerRpcHandlerTest.java +++ b/hummingbird-server/src/test/java/com/vaadin/server/communication/ServerRpcHandlerTest.java @@ -21,7 +21,12 @@ import com.vaadin.annotations.EventHandler; import com.vaadin.annotations.Tag; +import com.vaadin.hummingbird.StateNode; +import com.vaadin.hummingbird.dom.Element; +import com.vaadin.hummingbird.dom.TemplateElementStateProviderTest; +import com.vaadin.shared.JsonConstants; import com.vaadin.ui.Component; +import com.vaadin.ui.UI; import elemental.json.Json; import elemental.json.JsonArray; @@ -396,4 +401,33 @@ public void methodThrowsException_exceptionHasCorrectCause() { } } + public static void sendElementEvent(Element element, UI ui, + String eventType, JsonObject eventData) throws Exception { + JsonArray invocationsData = Json.createArray(); + invocationsData.set(0, + createElementEventInvocation(element, eventType, eventData)); + new ServerRpcHandler().handleInvocations(ui, 1, invocationsData); + } + + public static JsonObject createElementEventInvocation(Element element, + String eventType, JsonObject eventData) { + StateNode node = getInvocationNode(element); + // Copied from ServerConnector + JsonObject message = Json.createObject(); + message.put(JsonConstants.RPC_TYPE, JsonConstants.RPC_TYPE_EVENT); + message.put(JsonConstants.RPC_NODE, node.getId()); + message.put(JsonConstants.RPC_EVENT_TYPE, eventType); + + if (eventData != null) { + message.put(JsonConstants.RPC_EVENT_DATA, eventData); + } + + return message; + } + + private static StateNode getInvocationNode(Element element) { + return TemplateElementStateProviderTest.getOverrideNode(element) + .orElse(element.getNode()); + } + } diff --git a/hummingbird-server/src/test/java/com/vaadin/ui/TemplateTest.java b/hummingbird-server/src/test/java/com/vaadin/ui/TemplateTest.java index c515b116a7b..5881f80ea3b 100644 --- a/hummingbird-server/src/test/java/com/vaadin/ui/TemplateTest.java +++ b/hummingbird-server/src/test/java/com/vaadin/ui/TemplateTest.java @@ -18,6 +18,7 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.junit.After; @@ -38,6 +39,7 @@ import com.vaadin.hummingbird.router.ViewRendererTest.TestView; import com.vaadin.hummingbird.template.InlineTemplate; import com.vaadin.hummingbird.template.TemplateParseException; +import com.vaadin.server.communication.ServerRpcHandlerTest; import com.vaadin.ui.ComponentTest.TestComponent; /** @@ -332,4 +334,32 @@ public void checkThreadLocal() { Assert.assertNull(Component.elementToMapTo.get()); } + @Test + public void rootElementEventListener() throws Exception { + UI ui = new UI(); + Template t = new InlineTemplate(""); + Element element = t.getElement(); + ui.add(t); + AtomicInteger invoked = new AtomicInteger(0); + element.addEventListener("test-event", e -> { + invoked.incrementAndGet(); + }); + ServerRpcHandlerTest.sendElementEvent(element, ui, "test-event", null); + Assert.assertEquals(1, invoked.get()); + } + + @Test + public void childElementEventListener() throws Exception { + UI ui = new UI(); + Template t = new InlineTemplate(""); + Element element = t.getElement().getChild(0); + ui.add(t); + AtomicInteger invoked = new AtomicInteger(0); + element.addEventListener("test-event", e -> { + invoked.incrementAndGet(); + }); + ServerRpcHandlerTest.sendElementEvent(element, ui, "test-event", null); + Assert.assertEquals(1, invoked.get()); + } + } diff --git a/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.java b/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.java index 233b2d401a8..6f20a0bd74e 100644 --- a/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.java +++ b/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.java @@ -30,6 +30,8 @@ public class BasicTemplateView extends Template implements View { @Id("container") private Div container; + @Id("clearModel") + private Button clearModel; public BasicTemplateView() { assert container != null; @@ -49,11 +51,8 @@ public BasicTemplateView() { }); getElement().getNode().getFeature(TemplateMap.class) .setChild(childSlotContent.getElement().getNode()); - } - @EventHandler - private void clearModel() { - setModelValue(null); + clearModel.addClickListener(e -> setModelValue(null)); } @EventHandler diff --git a/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.java b/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.java new file mode 100644 index 00000000000..4836fc5963e --- /dev/null +++ b/hummingbird-tests/test-root-context/src/main/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2016 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.hummingbird.uitest.ui; + +import com.vaadin.annotations.Id; +import com.vaadin.hummingbird.dom.Element; +import com.vaadin.hummingbird.html.Button; +import com.vaadin.hummingbird.html.Div; +import com.vaadin.hummingbird.html.Span; +import com.vaadin.ui.Template; + +public class TemplateComponentMappingView extends Template { + + static final String LOG_ID = "log"; + + static final String BUTTON_ID = "button"; + + static final String SPAN_ID = "span"; + + static final String INPUT_ID = "input"; + + @Id(LOG_ID) + private Div log; + @Id(SPAN_ID) + private Span span; + @Id(BUTTON_ID) + private Button button; + + public TemplateComponentMappingView() { + span.getElement().addEventListener("click", + e -> logClick(e.getSource())); + button.addClickListener(e -> { + logClick(e.getSource().getElement()); + }); + } + + private void logClick(Element source) { + log(source.getAttribute("id") + " was clicked"); + } + + private void log(String message) { + getUI().get().getPage().executeJavaScript("log($0);", + "server: " + message); + } + +} diff --git a/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.html b/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.html index ae65e061b04..e5ac219d5fe 100644 --- a/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.html +++ b/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/BasicTemplateView.html @@ -3,7 +3,7 @@ @include otherfile.html@
@child@ - +
{{modelValue}}
diff --git a/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.html b/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.html new file mode 100644 index 00000000000..3661ea3260e --- /dev/null +++ b/hummingbird-tests/test-root-context/src/main/resources/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingView.html @@ -0,0 +1,17 @@ +
+ +
Main page
+ Span + +
+
+
diff --git a/hummingbird-tests/test-root-context/src/test/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingIT.java b/hummingbird-tests/test-root-context/src/test/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingIT.java new file mode 100644 index 00000000000..61b6059d67a --- /dev/null +++ b/hummingbird-tests/test-root-context/src/test/java/com/vaadin/hummingbird/uitest/ui/TemplateComponentMappingIT.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2016 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.hummingbird.uitest.ui; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.hummingbird.testutil.PhantomJSTest; + +public class TemplateComponentMappingIT extends PhantomJSTest { + + @Test + public void eventHandler() { + open(); + WebElement button = findElement( + By.id(TemplateComponentMappingView.BUTTON_ID)); + button.click(); + Assert.assertEquals("client: button was clicked\n" // + + "" + "server: button was clicked", getLog()); + WebElement span = findElement( + By.id(TemplateComponentMappingView.SPAN_ID)); + span.click(); + Assert.assertEquals("client: button was clicked\n" // + + "" + "server: button was clicked\n" // + + "client: span was clicked\n" // + + "server: span was clicked", getLog()); + } + + private String getLog() { + return findElement(By.id(TemplateComponentMappingView.LOG_ID)) + .getText(); + } +}