Skip to content

Commit

Permalink
Add support for overriding template element properties.
Browse files Browse the repository at this point in the history
Store all not bound properties inside override node. Delegate all
regular properties related method calls to override node.
Doesn't contain client side changes.
  • Loading branch information
Denis Anisimov authored and Artur- committed May 30, 2016
1 parent ab7166d commit 7d80454
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ 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);
Expand Down Expand Up @@ -263,29 +264,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 @@ -306,29 +307,48 @@ public EventRegistrationHandle addEventListener(StateNode node,

@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 +452,12 @@ public static StateNode createRootNode() {
public static StateNode createSubModelNode() {
return new StateNode(requiredFeatures);
}

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 @@ -58,7 +58,7 @@ public StateNode get(TemplateNode templateNode, boolean create) {
if (overrideNode == null && create) {
overrideNode = new StateNode(OverrideElementData.class,
ElementChildrenList.class, ParentGeneratorHolder.class,
ComponentMapping.class);
ComponentMapping.class, ElementPropertyMap.class);

overrideNode.getFeature(OverrideElementData.class)
.setTemplateNode(templateNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,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 +476,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

0 comments on commit 7d80454

Please sign in to comment.