forked from ampproject/amphtml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase-template.js
130 lines (116 loc) · 3.05 KB
/
base-template.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
import {isElement} from '#core/types';
import {Services} from '#service';
import {dev} from '#utils/log';
/**
* The interface that is implemented by all templates.
* @abstract
*/
export class BaseTemplate {
/**
* @param {!Element} element
* @param {!Window} win
*/
constructor(element, win) {
/** @public @const */
this.element = element;
/** @public @const {!Window} */
this.win = element.ownerDocument.defaultView || win;
/** @private @const */
this.viewer_ = Services.viewerForDoc(this.element);
this.compileCallback();
}
/**
* Override in subclass if the element needs to compile the template.
* @protected
*/
compileCallback() {
// Subclasses may override.
}
/**
* Bypasses template rendering and directly sets HTML. Should only be used
* for server-side rendering case. To be implemented by subclasses.
* @param {string} unusedData
* @return {!Element|!Array<Element>}
* @abstract
*/
setHtml(unusedData) {}
/**
* To be implemented by subclasses.
* @param {!JsonObject|string} unusedData
* @return {!Element}
* @abstract
*/
render(unusedData) {}
/**
* To be implemented by subclasses.
* @param {!JsonObject|string} unusedData
* @return {string}
* @abstract
*/
renderAsString(unusedData) {}
/**
* Iterate through the child nodes of the given root, applying the
* given callback to non-empty text nodes and elements.
* @param {!Element} root
* @param {function((!Element|string))} callback
*/
visitChildren_(root, callback) {
for (let n = root.firstChild; n != null; n = n.nextSibling) {
if (n.nodeType == /* TEXT */ 3) {
const text = n.textContent.trim();
if (text) {
callback(text);
}
} else if (n.nodeType == /* COMMENT */ 8) {
// Ignore comments.
} else if (isElement(n)) {
callback(dev().assertElement(n));
}
}
}
/**
* Unwraps the root element. If root has a single element child,
* returns the child. Otherwise, returns root.
* @param {!Element} root
* @return {!Element}
* @protected @final
*/
tryUnwrap(root) {
let onlyChild;
this.visitChildren_(root, (c) => {
if (onlyChild === undefined && c.nodeType) {
onlyChild = c;
} else {
onlyChild = null;
}
});
return onlyChild || root;
}
/**
* Unwraps the root element and returns any children in an array.
* Text node children are normalized inside a <div>.
* @param {!Element} root
* @return {!Array<!Element>}
* @protected @final
*/
unwrapChildren(root) {
const children = [];
this.visitChildren_(root, (c) => {
if (typeof c == 'string') {
const element = this.win.document.createElement('div');
element.textContent = c;
children.push(element);
} else {
children.push(c);
}
});
return children;
}
/**
* @protected @final
* @return {boolean}
*/
viewerCanRenderTemplates() {
return this.viewer_.hasCapability('viewerRenderTemplate');
}
}