Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read attribute value from template #8957

Merged
merged 8 commits into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ class LitTemplateDataAnalyzer implements Serializable {
private final Class<? extends LitTemplate> templateClass;

/**
* Create an instance of the analyzer using the {@code templateClass} and the
* template {@code parser}.
* Create an instance of the analyzer using the {@code templateClass} and
* the template {@code parser}.
*
* @param templateClass a template type
* @param templateClass
* a template type
*/
LitTemplateDataAnalyzer(Class<? extends LitTemplate> templateClass) {
this.templateClass = templateClass;
Expand All @@ -50,8 +51,9 @@ class LitTemplateDataAnalyzer implements Serializable {
ParserData parseTemplate() {
IdCollector idExtractor = new IdCollector(templateClass, null, null);
idExtractor.collectInjectedIds(Collections.emptySet());
return new ParserData(idExtractor.getIdByField(), idExtractor.getTagById(), Collections.emptySet(),
Collections.emptyList());
return new ParserData(idExtractor.getIdByField(),
idExtractor.getTagById(), Collections.emptyMap(),
Collections.emptySet(), Collections.emptyList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
package com.vaadin.flow.component.polymertemplate;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

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

import com.vaadin.flow.component.Component;
Expand All @@ -35,6 +37,7 @@
public class IdCollector {
private final Map<String, String> tagById = new HashMap<>();
private final Map<Field, String> idByField = new HashMap<>();
private final Map<String, Map<String, String>> attributesById = new HashMap<>();
private Element templateRoot;
private Class<?> templateClass;
private String templateFile;
Expand Down Expand Up @@ -103,7 +106,7 @@ private void collectedInjectedId(Field field,
: "(\"" + id + "\")"));
}

if (!addTagName(id, field)) {
if (!collectElementData(id, field)) {
throw new IllegalStateException(String.format(
"There is no element with "
+ "id='%s' in the template file '%s'. Cannot map it using @%s",
Expand All @@ -121,19 +124,21 @@ private void collectedInjectedId(Field field,
* @return <code>false</code> if the mapping did not pass validation,
* <code>true</code> otherwise
*/
private boolean addTagName(String id, Field field) {
private boolean collectElementData(String id, Field field) {
idByField.put(field, id);
if (templateRoot != null) {
// The template is available for parsing so check up front if the id
// exists
Optional<String> tagName = Optional
.ofNullable(templateRoot.getElementById(id))
Optional<Element> element = Optional
.ofNullable(templateRoot.getElementById(id));
Optional<String> tagName = element
.map(org.jsoup.nodes.Element::tagName);
if (tagName.isPresent()) {
if (element.isPresent()) {
denis-anisimov marked this conversation as resolved.
Show resolved Hide resolved
tagById.put(id, tagName.get());
denis-anisimov marked this conversation as resolved.
Show resolved Hide resolved
fetchAttributes(id, element.get().attributes());
}

return tagName.isPresent();
return element.isPresent();
}

return true;
Expand All @@ -157,4 +162,21 @@ public Map<String, String> getTagById() {
return tagById;
}

/**
* Gets a map from field ids to their parsed attributes values.
*
* @return a map from field ids to their parsed attributes values
*/
public Map<String, Map<String, String>> getAttributes() {
return Collections.unmodifiableMap(attributesById);
}

private void fetchAttributes(String id, Attributes attributes) {
if (attributes.size() == 0) {
return;
}
HashMap<String, String> attrs = new HashMap<>();
attributes.forEach(attr -> attrs.put(attr.getKey(), attr.getValue()));
attributesById.put(id, attrs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,25 +67,26 @@ public IdMapper(Component template) {
* @param tag
* the tag of the injected element or <code>null</code> if not
* known
* @param beforeComponentInject
* @param beforeInject
* a callback invoked before assigning the element/component to
* the field
*/
public void mapComponentOrElement(Field field, String id, String tag,
Consumer<Element> beforeComponentInject) {
injectClientSideElement(tag, id, field, beforeComponentInject);
Consumer<Element> beforeInject) {
injectClientSideElement(tag, id, field, beforeInject);
}

private Class<? extends Component> getContainerClass() {
return template.getClass();
}

private void injectClientSideElement(String tagName, String id, Field field,
Consumer<Element> beforeComponentInject) {
Consumer<Element> beforeInject) {
Class<?> fieldType = field.getType();

Tag tag = fieldType.getAnnotation(Tag.class);
if (tag != null && tagName != null && !tagName.equalsIgnoreCase(tag.value())) {
if (tag != null && tagName != null
&& !tagName.equalsIgnoreCase(tag.value())) {
String msg = String.format(
"Class '%s' has field '%s' whose type '%s' is annotated with "
+ "tag '%s' but the element defined in the HTML "
Expand All @@ -96,11 +97,12 @@ private void injectClientSideElement(String tagName, String id, Field field,
}
if (tag != null) {
// tag can be null if injecting Element
// tagName is the tag parsed from the template and it is null for Lit templates,
// tagName is the tag parsed from the template and it is null for
// Lit templates,
// which are not parsed
tagName = tag.value();
}
attachExistingElementById(tagName, id, field, beforeComponentInject);
attachExistingElementById(tagName, id, field, beforeInject);
}

/**
Expand Down Expand Up @@ -129,10 +131,12 @@ private Element getElement() {
* id of element to attach to
* @param field
* field to attach {@code Element} or {@code Component} to
* @param beforeComponentInject
* @param beforeInject
denis-anisimov marked this conversation as resolved.
Show resolved Hide resolved
* a callback invoked before assigning the element/component to
* the field
*/
private void attachExistingElementById(String tagName, String id,
Field field, Consumer<Element> beforeComponentInject) {
Field field, Consumer<Element> beforeInject) {
if (tagName == null) {
throw new IllegalArgumentException(
"Tag name parameter cannot be null");
Expand All @@ -146,15 +150,15 @@ private void attachExistingElementById(String tagName, String id,
list.append(element.getNode(), NodeProperties.INJECT_BY_ID, id);
registeredElementIdToInjected.put(id, element);
}
injectTemplateElement(element, field, beforeComponentInject);
injectTemplateElement(element, field, beforeInject);
}

@SuppressWarnings("unchecked")
private void injectTemplateElement(Element element, Field field,
Consumer<Element> beforeComponentInject) {
Consumer<Element> beforeInject) {
Class<?> fieldType = field.getType();
if (Component.class.isAssignableFrom(fieldType)) {
beforeComponentInject.accept(element);
beforeInject.accept(element);
Component component;

Optional<Component> wrappedComponent = element.getComponent();
Expand All @@ -167,6 +171,7 @@ private void injectTemplateElement(Element element, Field field,

ReflectTools.setJavaFieldValue(template, field, component);
} else if (Element.class.isAssignableFrom(fieldType)) {
beforeInject.accept(element);
ReflectTools.setJavaFieldValue(template, field, element);
} else {
String msg = String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public static class ParserData {
private final Map<String, String> tagById;
private final Map<Field, String> idByField;

private final Map<String, Map<String, String>> attributesById;

private final Set<String> twoWayBindingPaths;

private final Collection<SubTemplateData> subTemplates;
Expand All @@ -113,16 +115,20 @@ public static class ParserData {
* a map of fields to their ids
* @param tags
* a map of ids to their tags
* @param attributes
* a map of attributes values to the element id
* @param twoWayBindings
* the properties which support two way binding
* @param subTemplates
* data for sub templates
*/
public ParserData(Map<Field, String> fields, Map<String, String> tags,
Map<String, Map<String, String>> attributes,
Set<String> twoWayBindings,
Collection<SubTemplateData> subTemplates) {
tagById = Collections.unmodifiableMap(tags);
idByField = Collections.unmodifiableMap(fields);
attributesById = attributes;
twoWayBindingPaths = Collections.unmodifiableSet(twoWayBindings);
this.subTemplates = Collections
.unmodifiableCollection(subTemplates);
Expand All @@ -146,6 +152,14 @@ Set<String> getTwoWayBindingPaths() {
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 Expand Up @@ -254,7 +268,8 @@ public void tail(Node node, int depth) {

private ParserData readData(IdCollector idExtractor) {
return new ParserData(idExtractor.getIdByField(),
idExtractor.getTagById(), twoWayBindingPaths, subTemplates);
idExtractor.getTagById(), idExtractor.getAttributes(),
twoWayBindingPaths, subTemplates);
}

private String getTag(Class<? extends PolymerTemplate<?>> clazz) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

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

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.Uses;
Expand Down Expand Up @@ -153,9 +156,44 @@ private void attachComponentIfUses(Element element) {
/* Map declared fields marked @Id */

private void mapComponents() {
parserData.forEachInjectedField(
(field, id, tag) -> idMapper.mapComponentOrElement(field, id,
tag, this::attachComponentIfUses));
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));
attachComponentIfUses(element);
}));
}

private void setAttribute(Element element, String name, String value) {
if (name.endsWith("$")) {
denis-anisimov marked this conversation as resolved.
Show resolved Hide resolved
// 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() {
Expand Down Expand Up @@ -190,4 +228,8 @@ private static Map<String, Class<? extends Component>> extractUsesMap(

return map;
}

private static Logger getLogger() {
return LoggerFactory.getLogger(TemplateInitializer.class);
}
}