-
Notifications
You must be signed in to change notification settings - Fork 2
/
view.string.js
94 lines (85 loc) · 3.22 KB
/
view.string.js
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
'use strict';
// @fires blob.draw [view]
// @fires blob.update [view]
const he = require('he');
const {assert, is} = require('../utils');
// the tags appearing in this map will be represented as singletons.
const singletons = {
area: true,
base: true,
br: true,
col: true,
command: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true,
};
// target is used as a callback for the string output of rendering the vdom object.
const renderToString = (target, _vdom) => {
// string used to indent each level of the rendered dom.
const indentString = ' ';
assert(is.function(target), 'view.string.draw : target is not a function', target);
// the return value of this function is an array of lines. the reason for
// this is that nested tags need extra indentation and this function is
// recursive. extra spaces can easily be appended to each line appearing
// in the result of the render of a child.
const render = (vdom = {text: ''})=> {
if (is.defined(vdom.text)) {
return [he.encode(vdom.text)];
}
// the input of this function can be assumed to be proper vdom syntax
// since it has already been parsed and "transpiled" by the dom
// module's "build" blob.
let {tagName, attributes = {}, children = {}, childOrder = []} = vdom;
const formattedAttributes = Object.keys(attributes)
.map((key) => {
// since the class attribute is written as className in the
// vdom, a translation must be hardcoded.
if (key === 'className') {
key = 'class';
attributes.class = attributes.className;
}
let value = attributes[key];
if (value === undefined) {
value = '';
}
if (value === null) {
value = 'null';
}
return `${key}="${value.toString()}"`;
})
.join(' ');
// to correctly catch tags written with uppercase letters.
tagName = tagName.toLowerCase();
// early return in the case the element is a recognized singleton.
// there it also checks that the element does not have descendants.
if (is.defined(singletons[tagName]) && childOrder.length < 1) {
return [`<${`${tagName} ${formattedAttributes}`.trim()} />`];
}
const contents = childOrder
.map((key) => children[key])
// cannot use a simple map because render returns an array of lines
// which all need to be indented.
.reduce((acc, child) => acc.concat(render(child)), [])
.map((line) => indentString + line);
return [
`<${`${tagName} ${formattedAttributes}`.trim()}>`,
...contents,
`</${tagName}>`,
];
};
target(render(_vdom).join('\n'));
};
// blob generating function that is expected in the configuration object.
module.exports = ({send}) => {
send('blob.draw', renderToString);
send('blob.update', renderToString);
};