forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmaterializer.js
191 lines (179 loc) · 6.88 KB
/
materializer.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
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
// Turns HTMLjs into DOM nodes and DOMRanges.
//
// - `htmljs`: the value to materialize, which may be any of the htmljs
// types (Tag, CharRef, Comment, Raw, array, string, boolean, number,
// null, or undefined) or a View or Template (which will be used to
// construct a View).
// - `intoArray`: the array of DOM nodes and DOMRanges to push the output
// into (required)
// - `parentView`: the View we are materializing content for (optional)
// - `_existingWorkStack`: optional argument, only used for recursive
// calls when there is some other _materializeDOM on the call stack.
// If _materializeDOM called your function and passed in a workStack,
// pass it back when you call _materializeDOM (such as from a workStack
// task).
//
// Returns `intoArray`, which is especially useful if you pass in `[]`.
Blaze._materializeDOM = function (htmljs, intoArray, parentView,
_existingWorkStack) {
// In order to use fewer stack frames, materializeDOMInner can push
// tasks onto `workStack`, and they will be popped off
// and run, last first, after materializeDOMInner returns. The
// reason we use a stack instead of a queue is so that we recurse
// depth-first, doing newer tasks first.
var workStack = (_existingWorkStack || []);
materializeDOMInner(htmljs, intoArray, parentView, workStack);
if (! _existingWorkStack) {
// We created the work stack, so we are responsible for finishing
// the work. Call each "task" function, starting with the top
// of the stack.
while (workStack.length) {
// Note that running task() may push new items onto workStack.
var task = workStack.pop();
task();
}
}
return intoArray;
};
var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) {
if (htmljs == null) {
// null or undefined
return;
}
switch (typeof htmljs) {
case 'string': case 'boolean': case 'number':
intoArray.push(document.createTextNode(String(htmljs)));
return;
case 'object':
if (htmljs.htmljsType) {
switch (htmljs.htmljsType) {
case HTML.Tag.htmljsType:
intoArray.push(materializeTag(htmljs, parentView, workStack));
return;
case HTML.CharRef.htmljsType:
intoArray.push(document.createTextNode(htmljs.str));
return;
case HTML.Comment.htmljsType:
intoArray.push(document.createComment(htmljs.sanitizedValue));
return;
case HTML.Raw.htmljsType:
// Get an array of DOM nodes by using the browser's HTML parser
// (like innerHTML).
var nodes = Blaze._DOMBackend.parseHTML(htmljs.value);
for (var i = 0; i < nodes.length; i++)
intoArray.push(nodes[i]);
return;
}
} else if (HTML.isArray(htmljs)) {
for (var i = htmljs.length-1; i >= 0; i--) {
workStack.push(_.bind(Blaze._materializeDOM, null,
htmljs[i], intoArray, parentView, workStack));
}
return;
} else {
if (htmljs instanceof Blaze.Template) {
htmljs = htmljs.constructView();
// fall through to Blaze.View case below
}
if (htmljs instanceof Blaze.View) {
Blaze._materializeView(htmljs, parentView, workStack, intoArray);
return;
}
}
}
throw new Error("Unexpected object in htmljs: " + htmljs);
};
var materializeTag = function (tag, parentView, workStack) {
var tagName = tag.tagName;
var elem;
if ((HTML.isKnownSVGElement(tagName) || isSVGAnchor(tag))
&& document.createElementNS) {
// inline SVG
elem = document.createElementNS('http://www.w3.org/2000/svg', tagName);
} else {
// normal elements
elem = document.createElement(tagName);
}
var rawAttrs = tag.attrs;
var children = tag.children;
if (tagName === 'textarea' && tag.children.length &&
! (rawAttrs && ('value' in rawAttrs))) {
// Provide very limited support for TEXTAREA tags with children
// rather than a "value" attribute.
// Reactivity in the form of Views nested in the tag's children
// won't work. Compilers should compile textarea contents into
// the "value" attribute of the tag, wrapped in a function if there
// is reactivity.
if (typeof rawAttrs === 'function' ||
HTML.isArray(rawAttrs)) {
throw new Error("Can't have reactive children of TEXTAREA node; " +
"use the 'value' attribute instead.");
}
rawAttrs = _.extend({}, rawAttrs || null);
rawAttrs.value = Blaze._expand(children, parentView);
children = [];
}
if (rawAttrs) {
var attrUpdater = new ElementAttributesUpdater(elem);
var updateAttributes = function () {
var expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView);
var flattenedAttrs = HTML.flattenAttributes(expandedAttrs);
var stringAttrs = {};
for (var attrName in flattenedAttrs) {
stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName],
parentView,
HTML.TEXTMODE.STRING);
}
attrUpdater.update(stringAttrs);
};
var updaterComputation;
if (parentView) {
updaterComputation =
parentView.autorun(updateAttributes, undefined, 'updater');
} else {
updaterComputation = Tracker.nonreactive(function () {
return Tracker.autorun(function () {
Tracker._withCurrentView(parentView, updateAttributes);
});
});
}
Blaze._DOMBackend.Teardown.onElementTeardown(elem, function attrTeardown() {
updaterComputation.stop();
});
}
if (children.length) {
var childNodesAndRanges = [];
// push this function first so that it's done last
workStack.push(function () {
for (var i = 0; i < childNodesAndRanges.length; i++) {
var x = childNodesAndRanges[i];
if (x instanceof Blaze._DOMRange)
x.attach(elem);
else
elem.appendChild(x);
}
});
// now push the task that calculates childNodesAndRanges
workStack.push(_.bind(Blaze._materializeDOM, null,
children, childNodesAndRanges, parentView,
workStack));
}
return elem;
};
var isSVGAnchor = function (node) {
// We generally aren't able to detect SVG <a> elements because
// if "A" were in our list of known svg element names, then all
// <a> nodes would be created using
// `document.createElementNS`. But in the special case of <a
// xlink:href="...">, we can at least detect that attribute and
// create an SVG <a> tag in that case.
//
// However, we still have a general problem of knowing when to
// use document.createElementNS and when to use
// document.createElement; for example, font tags will always
// be created as SVG elements which can cause other
// problems. #1977
return (node.tagName === "a" &&
node.attrs &&
node.attrs["xlink:href"] !== undefined);
};