Skip to content

Commit

Permalink
Add support for event listeners in template elements (#884)
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur- committed May 31, 2016
1 parent ab7166d commit 1065cdd
Show file tree
Hide file tree
Showing 11 changed files with 392 additions and 21 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 @@ -126,10 +130,18 @@ public int size() {
ComponentMapping.class, TemplateMap.class,
ParentGeneratorHolder.class, TemplateEventHandlerNames.class };

@SuppressWarnings("unchecked")
private static Class<? extends NodeFeature>[] rootNodeFeatures = Stream
.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 @@ -144,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 @@ -263,29 +284,29 @@ public Element getChild(StateNode node, int index) {

@Override
public void insertChild(StateNode node, int index, Element child) {
modifyChildren(node, (provider, overrideNode) -> provider
modifyOverrideNode(node, (provider, overrideNode) -> provider
.insertChild(overrideNode, index, child));
}

@Override
public void removeChild(StateNode node, int index) {
modifyChildren(node, (provider, overrideNode) -> provider
modifyOverrideNode(node, (provider, overrideNode) -> provider
.removeChild(overrideNode, index));
}

@Override
public void removeChild(StateNode node, Element child) {
modifyChildren(node, (provider, overrideNode) -> provider
modifyOverrideNode(node, (provider, overrideNode) -> provider
.removeChild(overrideNode, child));
}

@Override
public void removeAllChildren(StateNode node) {
modifyChildren(node, (provider, overrideNode) -> provider
modifyOverrideNode(node, (provider, overrideNode) -> provider
.removeAllChildren(overrideNode));
}

private void modifyChildren(StateNode node,
private void modifyOverrideNode(StateNode node,
BiConsumer<BasicElementStateProvider, StateNode> modifier) {
if (templateNode.getChildCount() != 0) {
throw new IllegalStateException(
Expand All @@ -301,34 +322,56 @@ private void modifyChildren(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
public Object getProperty(StateNode node, String name) {
return templateNode.getPropertyBinding(name)
.map(binding -> binding.getValue(node)).orElse(null);
if (templateNode.getPropertyBinding(name).isPresent()) {
return templateNode.getPropertyBinding(name)
.map(binding -> binding.getValue(node)).orElse(null);
} else {
return getOverrideNode(node)
.map(overrideNode -> BasicElementStateProvider.get()
.getProperty(overrideNode, name))
.orElse(null);
}
}

@Override
public void setProperty(StateNode node, String name, Serializable value,
boolean emitChange) {
throw new UnsupportedOperationException(CANT_MODIFY_MESSAGE);
checkModifiableProperty(name);
modifyOverrideNode(node, (provider, overrideNode) -> provider
.setProperty(overrideNode, name, value, emitChange));
}

@Override
public void removeProperty(StateNode node, String name) {
throw new UnsupportedOperationException(CANT_MODIFY_MESSAGE);
checkModifiableProperty(name);
modifyOverrideNode(node, (provider, overrideNode) -> provider
.removeProperty(overrideNode, name));
}

@Override
public boolean hasProperty(StateNode node, String name) {
return templateNode.getPropertyBinding(name).isPresent();
return templateNode.getPropertyBinding(name).isPresent()
|| getOverrideNode(node)
.map(overrideNode -> BasicElementStateProvider.get()
.hasProperty(overrideNode, name))
.orElse(false);
}

@Override
public Stream<String> getPropertyNames(StateNode node) {
return templateNode.getPropertyNames();
Stream<String> regularProperties = getOverrideNode(node)
.map(BasicElementStateProvider.get()::getPropertyNames)
.orElse(Stream.empty());
return Stream.concat(templateNode.getPropertyNames(),
regularProperties);
}

@Override
Expand Down Expand Up @@ -432,4 +475,22 @@ public static StateNode createRootNode() {
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(
"Can't modify property '%s' with binding defined in a template",
name));
}
}
}
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);
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 All @@ -33,6 +34,7 @@
import org.junit.Test;

import com.vaadin.hummingbird.StateNode;
import com.vaadin.hummingbird.dom.impl.BasicElementStateProvider;
import com.vaadin.hummingbird.dom.impl.TemplateElementStateProvider;
import com.vaadin.hummingbird.nodefeature.ComponentMapping;
import com.vaadin.hummingbird.nodefeature.ModelMap;
Expand Down Expand Up @@ -475,6 +477,114 @@ public void dynamicClassNames() {
assertNotClassList(classList, "bar");
}

@Test
public void setProperty_regularProperty_elementDelegatesPropertyToOverrideNode() {
TemplateNode node = TemplateParser.parse("<div></div>",
new NullTemplateResolver());
Element element = createElement(node);
element.setProperty("prop", "foo");

StateNode overrideNode = element.getNode()
.getFeature(TemplateOverridesMap.class).get(node, false);
Assert.assertTrue(BasicElementStateProvider.get()
.hasProperty(overrideNode, "prop"));
Assert.assertEquals("foo", BasicElementStateProvider.get()
.getProperty(overrideNode, "prop"));
List<String> props = BasicElementStateProvider.get()
.getPropertyNames(overrideNode).collect(Collectors.toList());
Assert.assertEquals(1, props.size());
Assert.assertEquals("prop", props.get(0));
}

@Test
public void setProperty_regularProperty_hasPropertyAndHasProperValue() {
TemplateNode node = TemplateParser.parse("<div></div>",
new NullTemplateResolver());
Element element = createElement(node);
element.setProperty("prop", "foo");

Assert.assertTrue(element.hasProperty("prop"));
Assert.assertEquals("foo", element.getProperty("prop"));
List<String> props = element.getPropertyNames()
.collect(Collectors.toList());
Assert.assertEquals(1, props.size());
Assert.assertEquals("prop", props.get(0));
}

@Test
public void setRegularProperty_templateHasBoundProperty_hasPropertyAndHasProperValue() {
TemplateNode node = TemplateParser.parse("<div [foo]='bar'></div>",
new NullTemplateResolver());
Element element = createElement(node);
element.setProperty("prop", "foo");

Assert.assertTrue(element.hasProperty("prop"));
Assert.assertEquals("foo", element.getProperty("prop"));
Set<String> props = element.getPropertyNames()
.collect(Collectors.toSet());
Assert.assertEquals(2, props.size());
Assert.assertTrue(props.contains("foo"));
Assert.assertTrue(props.contains("prop"));
}

@Test
public void removeRegularProperty_templateHasBoundProperty_hasPropertyAndHasProperValue() {
TemplateNode node = TemplateParser.parse("<div [foo]='bar'></div>",
new NullTemplateResolver());
Element element = createElement(node);
element.setProperty("prop", "foo");

element.removeProperty("prop");

Assert.assertFalse(element.hasProperty("prop"));
Set<String> props = element.getPropertyNames()
.collect(Collectors.toSet());
Assert.assertEquals(1, props.size());
Assert.assertTrue(props.contains("foo"));
}

@Test
public void removeProperty_regularProperty_hasNoProperty() {
TemplateNode node = TemplateParser.parse("<div></div>",
new NullTemplateResolver());
Element element = createElement(node);
element.setProperty("prop", "foo");
element.removeProperty("prop");

Assert.assertFalse(element.hasProperty("prop"));
List<String> props = element.getPropertyNames()
.collect(Collectors.toList());
Assert.assertEquals(0, props.size());
}

@Test
public void removeProperty_regularProperty_elementDelegatesPropertyToOverrideNode() {
TemplateNode node = TemplateParser.parse("<div></div>",
new NullTemplateResolver());
Element element = createElement(node);
element.removeProperty("prop");

StateNode overrideNode = element.getNode()
.getFeature(TemplateOverridesMap.class).get(node, false);
Assert.assertFalse(BasicElementStateProvider.get()
.hasProperty(overrideNode, "prop"));
List<String> props = BasicElementStateProvider.get()
.getPropertyNames(overrideNode).collect(Collectors.toList());
Assert.assertEquals(0, props.size());
}

@Test(expected = IllegalArgumentException.class)
public void setProperty_boundProperty_throwException() {
Element element = createElement("<div [prop]='value'></div>");
element.setProperty("prop", "foo");
}

@Test(expected = IllegalArgumentException.class)
public void removeProperty_boundProperty_throwException() {
Element element = createElement("<div [prop]='value'></div>");
element.removeProperty("prop");
}

private void assertClassList(ClassList classList, String... expectedNames) {
HashSet<String> expectedSet = new HashSet<>(
Arrays.asList(expectedNames));
Expand Down Expand Up @@ -602,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());
}

}
Loading

0 comments on commit 1065cdd

Please sign in to comment.