-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathimported-template.html
205 lines (184 loc) · 9.73 KB
/
imported-template.html
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
<!--
imported-template.html version: 3.4.0
(c) 2013 Joachim Wester
MIT license
https://github.com/Juicy/imported-template
-->
<link rel="import" href="../juicy-html/juicy-html.html">
<script>
(function () {
// Needed to workaround https://github.com/webcomponents/webcomponentsjs/issues/1008
const isIE = /Trident/.test(navigator.userAgent) ||
/Edge\/\d./i.test(navigator.userAgent);
const importDisableType = 'import-disable';
const stripDisabledType = function(template) {
const scope = template.content || template;
scope.querySelectorAll(`link[type="${importDisableType}"]`)
.forEach((link)=>{link.removeAttribute('type')});
scope.querySelectorAll('template').forEach(stripDisabledType);
};
class ImportedTemplate extends customElements.get('juicy-html') {
constructor(self) {
self = super(self);
}
/**
* Import partial via HTML Import, and replicate its `<template>`.
* @IDEA: return promise (if supported) for document load (tomalec)
* @public only for debugging.
* @param {String} href partial URL
* @return {imported-template} self
*/
_loadExternalFile(href) {
//href is a URL, load the partial from the HTTP server/cache
var link = document.createElement('link');
link.rel = "import";
link.href = href;
var that = this;
this.pending = link;
link.onload = function processImportedDocument() {
// TODO(tomalec): caching
// HTML Imports polyfill starting from wc.js#1.x does not create separate document,
// it's just appended to `link` element, therefore there is no `body`
const importsBody = this.import instanceof Document ? this.import.body : this.import;
// find only root templates
let templates = this.import instanceof Document ?
this.import.querySelectorAll("head>template,body>template,imported-template-scope>template") :
Array.prototype.filter.call(
this.import.querySelectorAll("template"),
(e => e.parentNode === this.import || e.parentNode.tagName === 'IMPORTED-TEMPLATE-SCOPE')
);
var fragment, template;
var singleTemplate, singleFragment, nodes;
that.scopedNodes = [];
that.scopelessNodes = [];
that.clear();
if (templates.length >= 1) {
fragment = document.createDocumentFragment();
// clone templates contents, and mark correct scopes
// TODO: check if moving by text is faster,
// as we assume those are templates => document fragments (tomalec)
// IDEA(tomalec): skip all that magic if we do not have any imported-template-scopes
for (var nodeNo = 0; nodeNo < templates.length; nodeNo++) {
singleTemplate = templates[nodeNo];
//d debugger // or innerHTML in this case
singleFragment = document.importNode(singleTemplate.content, true);
if(isIE){
stripDisabledType(singleFragment);
}
// convert dynamic NodeList to regular array
nodes = Array.from(singleFragment.childNodes);
if (singleTemplate.parentElement.tagName === "IMPORTED-TEMPLATE-SCOPE") {
nodes.scope = singleTemplate.parentElement.getAttribute("scope");
that.scopedNodes.push(nodes);
} else {
that.scopelessNodes = that.scopelessNodes.concat(nodes);
}
fragment.appendChild(singleFragment);
}
} else if (importsBody.childNodes.length === 0 && (!this.import.head || this.import.head.childNodes.length === 0)){ // document is empty
console.warn('content given for imported-template is an empty document', this);
that.stampedNodes = null;
return;
} else { //there is no template in the response.
// it could be a stack trace, or HTML document with some nodes (HTML imports or scripts) that should / should not get stamped
console.error("DON'T misbehave! At least one <template> tag is expected in content body", importsBody.innerHTML);
return;
}
// convert dynamic NodeList to regular array
that.stampedNodes = Array.from(fragment.childNodes);
// dispatch event before stamping
that.dispatchEvent(new CustomEvent('stamping', {detail: fragment}));
// attach models
that.attributeChangedCallback("model", undefined, that.model || that.getAttribute("model"));
// Stamp tempalte into document
that.parentNode.insertBefore(fragment, that.nextSibling);
that.pending = false;
};
// Start importing
document.head.appendChild(link);
that.dispatchEvent(new CustomEvent("stamped", {
detail: that.stampedNodes
}));
return this;
}
/**
* Forward Polymer notification downwards from upper
* `<template is="dom-bind">`
* to the imported `<template is="dom-bind">`
* @param {String} path Polymer notification path
* @param {Mixed} value New value
*/
_notifyPath(path, value) {
return this._setPendingPropertyOrPath.call(this, path, value);
}
/**
* Forward Polymer notification downwards from `<dom-bind>`
* to the imported `<dom-bind>`
* @param {String} path Polymer notification path
* @param {Mixed} value New value
*/
_setPendingPropertyOrPath(path, value) {
if (!this.scopedNodes) {
//template not loaded yet. Nothing to forward
return;
}
var modelPrefix = 'model';
if (path.indexOf(modelPrefix) == 0) {
var childNo;
var scopedNodes = this.scopedNodes;
var len = scopedNodes && scopedNodes.length || 0;
for (childNo = 0; childNo < len; childNo++) {
var modelScopePrefix = modelPrefix + '.' + scopedNodes[childNo].scope;
if (path === modelScopePrefix) {
// notify the scope if it is the path
_notifyNodes(modelPrefix, value, scopedNodes[childNo]);
} else if (path.indexOf(modelScopePrefix + '.') === 0) {
// notify the scope if it is in path
var fixedScopePath = path.replace(modelScopePrefix, modelPrefix);
_notifyNodes(fixedScopePath, value, scopedNodes[childNo]);
}
}
_notifyNodes(path, value, this.scopelessNodes);
}
}
attachModel(model){
const attachModels = super.attachModel.bind(this);
var childNo,
scopedNodes = this.scopedNodes,
len = scopedNodes && scopedNodes.length || 0;
for (childNo = 0; childNo < len; childNo++) {
attachModels(model && model[scopedNodes[childNo].scope], scopedNodes[childNo]);
}
attachModels(model, this.scopelessNodes);
// inline HTML
if (!this.scopedNodes && !this.scopelessNodes) {
attachModels(model, this.stampedNodes);
}
}
}
var ImportedTemplatePrototype = ImportedTemplate.prototype;
/**
* Reference to pending (requested) HTMLLinkElement
* @type {HTMLLinkElement}
*/
ImportedTemplatePrototype.pending = false;
ImportedTemplatePrototype.scopedNodes = null;
// to fool Polymer into thinking `imported-template` is a polymer element thus forwarding notifications to it.
ImportedTemplatePrototype.__dataHasAccessor = {partial: true, viewModel: true, model: true};
ImportedTemplatePrototype.__isPropertyEffectsClient = true;
// Polymer doesn't set props on its own components, rather, it calls this function
ImportedTemplatePrototype._setPendingProperty = function (path, value) {
this[path] = value;
}
function _notifyNodes(path, value, nodes) {
for (var childNo = 0; childNo < nodes.length; childNo++) {
if (nodes[childNo].notifyPath) {
nodes[childNo].notifyPath(path, value);
} else if (nodes[childNo]._notifyPath) {
nodes[childNo]._notifyPath(path, value);
}
}
}
customElements.define('imported-template', ImportedTemplate);
})();
</script>