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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
|
/*
* Copyright 2000-2018 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.ui;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jsoup.nodes.Element;
import com.vaadin.server.JsonPaintTarget;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.shared.ui.customlayout.CustomLayoutState;
import com.vaadin.ui.declarative.DesignContext;
/**
* <p>
* A container component with freely designed layout and style. The layout
* consists of items with textually represented locations. Each item contains
* one sub-component, which can be any Vaadin component, such as a layout. The
* adapter and theme are responsible for rendering the layout with a given style
* by placing the items in the defined locations.
* </p>
*
* <p>
* The placement of the locations is not fixed - different themes can define the
* locations in a way that is suitable for them. One typical example would be to
* create visual design for a web site as a custom layout: the visual design
* would define locations for "menu", "body", and "title", for example. The
* layout would then be implemented as an HTML template for each theme.
* </p>
*
* <p>
* A location is identified with the attribute "data-location" or "location"
* which has the location name as its value.
* </p>
*
* <p>
* The default theme handles the styles that are not defined by drawing the
* subcomponents just as in OrderedLayout.
* </p>
*
* @author Vaadin Ltd.
* @since 3.0
*/
@SuppressWarnings("serial")
public class CustomLayout extends AbstractLayout implements LegacyComponent {
private static final int BUFFER_SIZE = 10000;
/**
* Custom layout slots containing the components.
*/
private final Map<String, Component> slots = new HashMap<>();
/**
* Default constructor only used by subclasses. Subclasses are responsible
* for setting the appropriate fields. Either
* {@link #setTemplateName(String)}, that makes layout fetch the template
* from theme, or {@link #setTemplateContents(String)}.
*
* @since 7.5.0
*/
public CustomLayout() {
setWidth(100, Unit.PERCENTAGE);
}
/**
* Constructs a custom layout with the template given in the stream.
*
* @param templateStream
* Stream containing template data. Must be using UTF-8 encoding.
* To use a String as a template use for instance new
* ByteArrayInputStream("<template>".getBytes()).
* @throws IOException
*/
public CustomLayout(InputStream templateStream) throws IOException {
this();
initTemplateContentsFromInputStream(templateStream);
}
/**
* Constructor for custom layout with given template name. Template file is
* fetched from "<theme>/layout/<templateName>".
*/
public CustomLayout(String template) {
this();
setTemplateName(template);
}
protected void initTemplateContentsFromInputStream(
InputStream templateStream) throws IOException {
BufferedReader reader = new BufferedReader(
new InputStreamReader(templateStream, UTF_8));
StringBuilder builder = new StringBuilder(BUFFER_SIZE);
try {
char[] cbuf = new char[BUFFER_SIZE];
int nRead;
while ((nRead = reader.read(cbuf, 0, BUFFER_SIZE)) > 0) {
builder.append(cbuf, 0, nRead);
}
} finally {
reader.close();
}
setTemplateContents(builder.toString());
}
@Override
protected CustomLayoutState getState() {
return (CustomLayoutState) super.getState();
}
@Override
protected CustomLayoutState getState(boolean markAsDirty) {
return (CustomLayoutState) super.getState(markAsDirty);
}
/**
* Adds the component into this container to given location. If the location
* is already populated, the old component is removed.
*
* @param c
* the component to be added.
* @param location
* the location of the component.
*/
public void addComponent(Component c, String location) {
final Component old = slots.get(location);
if (old != null) {
removeComponent(old);
}
slots.put(location, c);
getState().childLocations.put(c, location);
super.addComponent(c);
}
/**
* Adds the component into this container. The component is added without
* specifying the location (empty string is then used as location). Only one
* component can be added to the default "" location and adding more
* components into that location overwrites the old components.
*
* @param c
* the component to be added.
*/
@Override
public void addComponent(Component c) {
this.addComponent(c, "");
}
/**
* Removes the component from this container.
*
* @param c
* the component to be removed.
*/
@Override
public void removeComponent(Component c) {
if (c == null) {
return;
}
slots.values().remove(c);
getState().childLocations.remove(c);
super.removeComponent(c);
}
/**
* Removes the component from this container from given location.
*
* @param location
* the Location identifier of the component.
*/
public void removeComponent(String location) {
this.removeComponent(slots.get(location));
}
/**
* Gets the component container iterator for going trough all the components
* in the container.
*
* @return the Iterator of the components inside the container.
*/
@Override
public Iterator<Component> iterator() {
return Collections.unmodifiableCollection(slots.values()).iterator();
}
/**
* Gets the number of contained components. Consistent with the iterator
* returned by {@link #getComponentIterator()}.
*
* @return the number of contained components
*/
@Override
public int getComponentCount() {
return slots.values().size();
}
/**
* Gets the child-component by its location.
*
* @param location
* the name of the location where the requested component
* resides.
* @return the Component in the given location or null if not found.
*/
public Component getComponent(String location) {
return slots.get(location);
}
/* Documented in superclass */
@Override
public void replaceComponent(Component oldComponent,
Component newComponent) {
// Gets the locations
String oldLocation = null;
String newLocation = null;
for (final String location : slots.keySet()) {
final Component component = slots.get(location);
if (component == oldComponent) {
oldLocation = location;
}
if (component == newComponent) {
newLocation = location;
}
}
if (oldLocation == null) {
addComponent(newComponent);
} else if (newLocation == null) {
removeComponent(oldLocation);
addComponent(newComponent, oldLocation);
} else {
slots.put(newLocation, oldComponent);
slots.put(oldLocation, newComponent);
getState().childLocations.put(newComponent, oldLocation);
getState().childLocations.put(oldComponent, newLocation);
}
}
/** Get the name of the template. */
public String getTemplateName() {
return getState(false).templateName;
}
/** Get the contents of the template. */
public String getTemplateContents() {
return getState(false).templateContents;
}
/**
* Set the name of the template used to draw custom layout.
*
* With GWT-adapter, the template with name 'templatename' is loaded from
* VAADIN/themes/themename/layouts/templatename.html. If the theme has not
* been set (with Application.setTheme()), themename is 'default'.
*
* @param templateName
*/
public void setTemplateName(String templateName) {
getState().templateName = templateName;
getState().templateContents = null;
}
/**
* Set the contents of the template used to draw the custom layout.
*
* @param templateContents
*/
public void setTemplateContents(String templateContents) {
getState().templateContents = templateContents;
getState().templateName = null;
}
@Override
public void changeVariables(Object source, Map<String, Object> variables) {
// Nothing to see here
}
@Override
public void paintContent(PaintTarget target) throws PaintException {
// Workaround to make the CommunicationManager read the template file
// and send it to the client
String templateName = getState(false).templateName;
if (templateName != null && !templateName.isEmpty()) {
Set<Object> usedResources = ((JsonPaintTarget) target)
.getUsedResources();
String resourceName = "layouts/" + templateName + ".html";
usedResources.add(resourceName);
}
}
@Override
public void readDesign(Element design, DesignContext designContext) {
super.readDesign(design, designContext);
for (Element child : design.children()) {
Component childComponent = designContext.readDesign(child);
if (child.hasAttr(":location")) {
addComponent(childComponent, child.attr(":location"));
} else {
addComponent(childComponent);
}
}
}
@Override
public void writeDesign(Element design, DesignContext designContext) {
super.writeDesign(design, designContext);
for (Entry<String, Component> slot : slots.entrySet()) {
Element child = designContext.createElement(slot.getValue());
if (slots.size() > 1 || !"".equals(slot.getKey())) {
child.attr(":location", slot.getKey());
}
design.appendChild(child);
}
}
}
|