Skip to content

Commit

Permalink
Read template attributes as properties (#8988)
Browse files Browse the repository at this point in the history
Read template attributes as properties

Fixes #8974
# Conflicts:
#	flow-server/src/main/java/com/vaadin/flow/component/littemplate/LitTemplateDataAnalyzer.java
#	flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/IdCollector.java
#	flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/TemplateDataAnalyzer.java
#	flow-server/src/main/java/com/vaadin/flow/component/polymertemplate/TemplateInitializer.java
#	flow-server/src/test/java/com/vaadin/flow/component/polymertemplate/PolymerTemplateTest.java
#	flow-tests/test-root-context/frontend/AttributeTemplate.js
#	flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/template/TemplateAttributeView.java
#	flow-tests/test-root-context/src/test/java/com/vaadin/flow/uitest/ui/template/TemplateAttributeIT.java
  • Loading branch information
Denis authored and pleku committed Feb 2, 2021
1 parent ede3f83 commit 0991728
Show file tree
Hide file tree
Showing 25 changed files with 934 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2000-2020 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.flow.component.littemplate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.template.internal.AbstractInjectableElementInitializer;
import com.vaadin.flow.dom.Element;

/**
* Initialize a lit template element with data.
*
* @author Vaadin Ltd
* @since
*
*/
public class InjectableLitElementInitializer
extends AbstractInjectableElementInitializer {

private static final String DYNAMIC_ATTRIBUTE_PREFIX = "Template {} contains an attribute {} in element {} which";
private final Class<? extends Component> templateClass;

/**
* Creates an initializer for the {@code element}.
*
* @param element
* element to initialize
* @param templateClass
* the class of the template component
*/
public InjectableLitElementInitializer(Element element,
Class<? extends Component> templateClass) {
super(element);
this.templateClass = templateClass;
}

@Override
protected boolean isStaticAttribute(String name, String value) {
if (name.startsWith("?")) {
// this is a boolean attribute binding, ignore it since we don't
// support bindings: the value is not an expression
getLogger().debug(
"{} starts with '?' and ignored by initialization since this is an attribute binding",
DYNAMIC_ATTRIBUTE_PREFIX, templateClass.getSimpleName(),
name, getElement().getTag());
return false;
}
if (name.startsWith(".")) {
// this is a property binding, ignore it since we don't support
// bindings: the value is not an expression
getLogger().debug(
"{} starts with '.' and ignored by initialization since this is a property binding",
DYNAMIC_ATTRIBUTE_PREFIX, templateClass.getSimpleName(),
name, getElement().getTag());
return false;
}
if (name.startsWith("@")) {
// this is an event listener
getLogger().debug(
"{} starts with '@' and ignored by initialization since this is an event listener declration",
DYNAMIC_ATTRIBUTE_PREFIX, templateClass.getSimpleName(),
name, getElement().getTag());
return false;
}
if (value == null) {
return true;
}
if (value.contains("${") && value.contains("}")) {
// this is a dynamic value
getLogger().debug(
"Template {} contains an attribute {} in element {} whose value"
+ " is dynamic and it's ignored by initilization",
templateClass.getSimpleName(), name, getElement().getTag());
return false;
}
return true;
}

private static Logger getLogger() {
return LoggerFactory.getLogger(InjectableLitElementInitializer.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ protected LitTemplate() {
*/
protected LitTemplate(LitTemplateParser parser, VaadinService service) {
LitTemplateInitializer templateInitializer = new LitTemplateInitializer(
this, VaadinService.getCurrent());
this, parser, service);
templateInitializer.initChildElements();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ ParserData parseTemplate() {
templateRoot);
idExtractor.collectInjectedIds(Collections.emptySet());
return new ParserData(idExtractor.getIdByField(),
idExtractor.getTagById(), Collections.emptyMap(),
idExtractor.getTagById(), idExtractor.getAttributes(),
Collections.emptySet(), Collections.emptyList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@
package com.vaadin.flow.component.littemplate;

import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import com.vaadin.flow.component.littemplate.LitTemplateParser.LitTemplateParserFactory;
import com.vaadin.flow.component.polymertemplate.IdMapper;
import com.vaadin.flow.component.polymertemplate.TemplateDataAnalyzer.ParserData;
import com.vaadin.flow.di.Instantiator;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.internal.ReflectionCache;
import com.vaadin.flow.server.VaadinService;

Expand All @@ -40,6 +38,8 @@ public class LitTemplateInitializer {

private final ParserData parserData;

private final Class<? extends LitTemplate> templateClass;

/**
* Creates a new initializer instance.
* <p>
Expand Down Expand Up @@ -79,7 +79,7 @@ public LitTemplateInitializer(LitTemplate template, VaadinService service) {
boolean productionMode = service.getDeploymentConfiguration()
.isProductionMode();

Class<? extends LitTemplate> templateClass = template.getClass();
templateClass = template.getClass();

ParserData data = null;
if (productionMode) {
Expand All @@ -101,12 +101,13 @@ public LitTemplateInitializer(LitTemplate template, VaadinService service) {
*/
public void initChildElements() {
IdMapper idMapper = new IdMapper(template);
Consumer<Element> noOp = element -> {
// Nothing to do for elements
};

parserData.forEachInjectedField((field, id, tag) -> idMapper
.mapComponentOrElement(field, id, tag, noOp));
parserData.forEachInjectedField(
(field, id, tag) -> idMapper.mapComponentOrElement(field, id,
tag,
element -> new InjectableLitElementInitializer(element,
templateClass)
.accept(parserData.getAttributes(id))));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Set;
import java.util.stream.Stream;

import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;

Expand Down Expand Up @@ -177,7 +178,20 @@ private void fetchAttributes(String id, Attributes attributes) {
return;
}
HashMap<String, String> attrs = new HashMap<>();
attributes.forEach(attr -> attrs.put(attr.getKey(), attr.getValue()));
attributes.forEach(attr -> setAttributeData(attr, attrs));
attributesById.put(id, attrs);
}

private void setAttributeData(Attribute attribute,
HashMap<String, String> data) {
if (isBooleanAttribute(attribute)) {
data.put(attribute.getKey(), Boolean.TRUE.toString());
} else {
data.put(attribute.getKey(), attribute.getValue());
}
}

private boolean isBooleanAttribute(Attribute attribute) {
return attribute.getKey().equals(attribute.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2000-2020 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.flow.component.polymertemplate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.template.internal.AbstractInjectableElementInitializer;
import com.vaadin.flow.dom.Element;

/**
* Initialize a polymer template element with data.
*
* @author Vaadin Ltd
* @since
*
*/
public class InjectablePolymerElementInitializer
extends AbstractInjectableElementInitializer {

private static final String DYNAMIC_ATTRIBUTE_PREFIX = "Template {} contains an attribute {} in element {} whose value";
private final Class<? extends Component> templateClass;

/**
* Creates an initializer for the {@code element}.
*
* @param element
* element to initialize
* @param templateClass
* the class of the template component
*/
public InjectablePolymerElementInitializer(Element element,
Class<? extends Component> templateClass) {
super(element);
this.templateClass = templateClass;
}

@Override
protected boolean isStaticAttribute(String name, String value) {
if (name.endsWith("$")) {
// this is an attribute binding, ignore it since we don't support
// bindings: the value is not an expression
getLogger().debug(
"Template {} contains an attribute {} in element {} which "
+ "ends with $ and ignored by initialization since this is an attribute binding",
templateClass.getSimpleName(), name, getElement().getTag());
return false;
}
if (value == null) {
return true;
}
if (value.contains("{{") && value.contains("}}")) {
// this is a binding, skip it
getLogger().debug(
"{} contains two-way binding and it's ignored by initilization",
DYNAMIC_ATTRIBUTE_PREFIX, templateClass.getSimpleName(),
name, getElement().getTag());
return false;
}
if (value.contains("[[") && value.contains("]]")) {
// this is another binding, skip it
getLogger().debug(
"{} contains binding and it's ignored by initilization",
DYNAMIC_ATTRIBUTE_PREFIX, templateClass.getSimpleName(),
name, getElement().getTag());
return false;
}
if (value.contains("${") && value.contains("}")) {
// this is a dynamic value
getLogger().debug("{} is dynamic and it's ignored by initilization",
DYNAMIC_ATTRIBUTE_PREFIX, templateClass.getSimpleName(),
name, getElement().getTag());
return false;
}
return true;
}

private static Logger getLogger() {
return LoggerFactory
.getLogger(InjectablePolymerElementInitializer.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public ParserData(Map<Field, String> fields, Map<String, String> tags,
Collection<SubTemplateData> subTemplates) {
tagById = Collections.unmodifiableMap(tags);
idByField = Collections.unmodifiableMap(fields);
attributesById = attributes;
attributesById = Collections.unmodifiableMap(attributes);
twoWayBindingPaths = Collections.unmodifiableSet(twoWayBindings);
this.subTemplates = Collections
.unmodifiableCollection(subTemplates);
Expand All @@ -145,21 +145,28 @@ public void forEachInjectedField(InjectableFieldCunsumer consumer) {
(field, id) -> consumer.apply(field, id, tagById.get(id)));
}

/**
* Gets template element data (attribute values).
*
* @param id
* the id of the element
* @return template data
*/
public Map<String, String> getAttributes(String id) {
Map<String, String> attrs = attributesById.get(id);
if (attrs == null) {
return Collections.emptyMap();
}
return attrs;
}

Set<String> getTwoWayBindingPaths() {
return twoWayBindingPaths;
}

void forEachSubTemplate(Consumer<SubTemplateData> dataConsumer) {
subTemplates.forEach(dataConsumer);
}

Map<String, String> getAttributes(String id) {
Map<String, String> attrs = attributesById.get(id);
if (attrs == null) {
return Collections.emptyMap();
}
return attrs;
}
}

static class SubTemplateData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,44 +158,13 @@ private void attachComponentIfUses(Element element) {
private void mapComponents() {
parserData.forEachInjectedField((field, id, tag) -> idMapper
.mapComponentOrElement(field, id, tag, element -> {
Map<String, String> attributes = parserData
.getAttributes(id);
attributes.forEach((name, value) -> setAttribute(element,
name, value));
InjectablePolymerElementInitializer initializer = new InjectablePolymerElementInitializer(
element, templateClass);
initializer.accept(parserData.getAttributes(id));
attachComponentIfUses(element);
}));
}

private void setAttribute(Element element, String name, String value) {
if (name.endsWith("$")) {
// this is an attribute binding, ignore it since we don't support
// bindings: the value is not an expression
getLogger().debug(
"Template {} contains an attribute {} in element {} which "
+ "ends with $ and ignored by initialization since this is an attribute binding",
templateClass.getSimpleName(), name, element.getTag());
return;
}
if (value.contains("{{") && value.contains("}}")) {
// this is a binding, skip it
getLogger().debug(
"Template {} contains an attribute {} in element {} whose value"
+ " contains two-way binding and it's ignored by initilization",
templateClass.getSimpleName(), name, element.getTag());
return;
}
if (value.contains("[[") && value.contains("]]")) {
// this is another binding, skip it
getLogger().debug(
"Template {} contains an attribute {} in element {} whose value"
+ " contains binding and it's ignored by initilization",
templateClass.getSimpleName(), name, element.getTag());
return;
}
// anything else is considered as an attribute value
element.setAttribute(name, value);
}

private Element getElement() {
return template.getElement();
}
Expand Down

0 comments on commit 0991728

Please sign in to comment.