1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework.layout;
35
36 import static com.vaadin.ui.declarative.Design.*;
37
38 import info.magnolia.freemarker.FreemarkerHelper;
39 import info.magnolia.resourceloader.ResourceOrigin;
40 import info.magnolia.ui.field.EditorPropertyDefinition;
41
42 import java.io.ByteArrayInputStream;
43 import java.io.InputStream;
44 import java.io.Reader;
45 import java.io.StringWriter;
46 import java.nio.charset.StandardCharsets;
47 import java.util.HashMap;
48 import java.util.Map;
49 import java.util.Optional;
50
51 import javax.inject.Inject;
52
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import com.vaadin.data.Binder;
57 import com.vaadin.data.HasValue;
58 import com.vaadin.ui.Component;
59 import com.vaadin.ui.Label;
60 import com.vaadin.ui.Layout;
61 import com.vaadin.ui.VerticalLayout;
62 import com.vaadin.ui.declarative.Design;
63 import com.vaadin.ui.declarative.DesignContext;
64
65
66
67
68
69
70
71
72 public class DeclarativeLayoutProducer implements FieldLayoutProducer<DeclarativeLayoutDefinition> {
73
74 private static final Logger log = LoggerFactory.getLogger(DeclarativeLayoutProducer.class);
75
76 private FreemarkerHelper freemarkerHelper;
77 private ResourceOrigin resourceOrigin;
78 private final Binder binder;
79
80 @Inject
81 public DeclarativeLayoutProducer(FreemarkerHelper freemarkerHelper, ResourceOrigin resourceOrigin, Binder binder) {
82 this.freemarkerHelper = freemarkerHelper;
83 this.resourceOrigin = resourceOrigin;
84 this.binder = binder;
85 }
86
87 @Override
88 public Layout createLayout(DeclarativeLayoutDefinition definition, Map<EditorPropertyDefinition, Component> mappings) {
89 Design.ComponentMapper defaultComponentMapper = getComponentMapper();
90 try (final Reader templateReader = resourceOrigin.getByPath(definition.getTemplate()).openReader()) {
91 StringWriter ftlEnhancedTemplateWriter = new StringWriter();
92 freemarkerHelper.render(templateReader, new HashMap<>(), ftlEnhancedTemplateWriter);
93 InputStream enhancedTemplateInputStream = new ByteArrayInputStream(ftlEnhancedTemplateWriter.toString().getBytes(StandardCharsets.UTF_8));
94 setComponentMapper(new MagnoliaComponentMapper(defaultComponentMapper, mappings));
95 Layout../../../../../info/magnolia/ui/field/Layout.html#Layout">Layout layout = (Layout) Design.read(enhancedTemplateInputStream);
96 layout.setStyleName("declarative-layout");
97 return layout;
98 } catch (Exception e) {
99 log.error(String.format("Error while processing the template %s.", definition.getTemplate()), e);
100 Label label = new Label(String.format("%s: %s", e.getClass().getName(), e.getMessage()));
101 label.setStyleName("error-message");
102 Layout errorLayout = new VerticalLayout(label);
103 errorLayout.setStyleName("declarative-layout");
104 return errorLayout;
105 } finally {
106 setComponentMapper(defaultComponentMapper);
107 }
108 }
109
110 private class MagnoliaComponentMapper implements Design.ComponentMapper {
111 private static final String FORM_TAG_PREFIX = "form-";
112 private Design.ComponentMapper defaultComponentMapper;
113 private Map<String, Component> componentsByName;
114
115 MagnoliaComponentMapper(Design.ComponentMapper defaultComponentMapper, Map<EditorPropertyDefinition, Component> mappings) {
116 this.defaultComponentMapper = defaultComponentMapper;
117 this.componentsByName = new HashMap<>();
118 mappings.forEach((definition, component) -> addComponentMapping(componentsByName, definition.getName(), component));
119 }
120
121 private void addComponentMapping(Map<String, Component> componentsByName, String componentName, Component component) {
122 String normalizedComponentName = componentName.toLowerCase();
123 if (componentsByName.containsKey(normalizedComponentName)) {
124 throw new IllegalArgumentException(String.format("Only one component with the name %s is allowed in the declarative design.", normalizedComponentName));
125 }
126 componentsByName.put(normalizedComponentName, component);
127 }
128
129 @Override
130 public Component tagToComponent(String tag, Design.ComponentFactory componentFactory, DesignContext context) {
131 if (context.getPackage("mgnl") == null) {
132 context.addPackagePrefix("mgnl", "info.magnolia.ui.vaadin.form.custom");
133 }
134 if (!tag.startsWith(FORM_TAG_PREFIX)) {
135 Component component = defaultComponentMapper.tagToComponent(tag, componentFactory, context);
136 if (component instanceof HasValue) {
137 context.addComponentCreationListener(event -> {
138 String componentLocalId = context.getComponentLocalId(component);
139 if (!binder.getBinding(componentLocalId).isPresent()) {
140 binder.bind((HasValue<?>) component, componentLocalId);
141 }
142 });
143 }
144 return component;
145 }
146 String normalizedComponentName = tag.substring(FORM_TAG_PREFIX.length());
147 Component magnoliaComponent = componentsByName.get(normalizedComponentName);
148 if (magnoliaComponent == null) {
149 throw new IllegalArgumentException(String.format("Cannot find magnolia component %s in the current mappings.", normalizedComponentName));
150 }
151 return magnoliaComponent;
152 }
153
154 @Override
155 public String componentToTag(Component component, DesignContext context) {
156 Optional<String> optionalComponentName = getNameFromComponent(component);
157 return optionalComponentName.map(componentName -> FORM_TAG_PREFIX + componentName)
158 .orElseGet(() -> defaultComponentMapper.componentToTag(component, context));
159 }
160
161 private Optional<String> getNameFromComponent(Component component) {
162 return componentsByName.entrySet().stream()
163 .filter(entry -> entry.getValue().equals(component))
164 .map(Map.Entry::getKey)
165 .findFirst();
166 }
167 }
168 }