Skip to content

Commit

Permalink
Add support for event listeners in template elements
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur- committed May 31, 2016
1 parent 7d80454 commit 906c9ac
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -131,6 +135,13 @@ public int size() {
.concat(Stream.of(requiredFeatures), Stream.of(rootOnlyFeatures))
.toArray(Class[]::new);

@SuppressWarnings("unchecked")
private static Class<? extends NodeFeature>[] 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;

Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <code>null</code>
*/
public static StateNode createOverrideNode() {
return new StateNode(overrideNodeFeatures);
}

private void checkModifiableProperty(String name) {
if (templateNode.getPropertyBinding(name).isPresent()) {
throw new IllegalArgumentException(String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -711,4 +712,18 @@ public static Element createElement(TemplateNode templateNode) {

return Element.get(stateNode);
}

public static Optional<StateNode> 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));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

}
30 changes: 30 additions & 0 deletions hummingbird-server/src/test/java/com/vaadin/ui/TemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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("<root><child></child></root>");
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("<root><child></child></root>");
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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
@include otherfile.html@
<div id="container"></div>
@child@
<button id="clearModel" (click)="$server.clearModel()">Clear model value</button>
<button id="clearModel">Clear model value</button>
<button id="modelText" (click)="$server.setModelText()">Set text model value</button>
<button id="modelBoolean" (click)="$server.setModelBoolean()">Set text boolean value</button>
<div id="bindings" [title]="modelValue" [class.name]="modelValue">{{modelValue}}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div>
<script>
var log = function(msg) {
var d = document.createElement('div');
d.innerText = msg;
document.getElementById("log").insertBefore(d, null);
}
var logClick = function(element) {
log('client: ' + element.id + ' was clicked');
}
</script>
<div>Main page</div>
<span id="span" (click)="logClick($event.target);">Span</span>
<button id="button" (click)="logClick($event.target);">Button</button>
<hr />
<div id="log"></div>
</div>
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 906c9ac

Please sign in to comment.