#javascript #html #web-component #shadow-dom
#javascript #HTML #веб-компонент #shadow-dom
Вопрос:
Я хотел бы повторно использовать свои html-компоненты, содержащие некоторый код javascript, поэтому для упрощения я приведу один простой пример:
index.html:
<!DOCTYPE html>
<html>
<head></head>
<body>
<my-component></my-component>
<script src="index.js"></script>
</body>
</html>
my-component.html:
<template>
<div id="something"></div>
<script>
// It doesn't work, this here is "window"
document.getElementById("something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, {mode: "no-cors"})
.then(res => res.text())
.then(html => {
const parser = new DOMParser()
const document = parser.parseFromString(html, 'text/html')
const head = document.head
const template = head.querySelector('template')
return template
})
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'})
shadow.appendChild(document.importNode(template.content, true))
}
}
return customElements.define(name, UnityComponent)
}
function loadComponent (name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template))
}
return {loadComponent}
}())
makeComponent.loadComponent("my-component", "my-component.html")
Я могу с помощью этого кода, но он копирует все переменные скрипта в window:
<template>
<div id="something"></div>
<style onload="templFunc.apply(this.getRootNode())"></style>
<script>
function templFunc() {
// It works
let text = "Something"
this.querySelector('#something').innerHTML = text
// but...
console.log(window.text) // => "Something"
}
</script>
</template>
Это не имеет смысла, если скрипт находится внутри шаблона, по крайней мере, должен иметь доступ к элементам внутри шаблона, иначе шаблон почти не используется для javascript, поэтому я не могу понять намерение использовать скрипт внутри шаблона или как повторно использовать веб-компоненты, использующие javascriptЭто неправильно?
Итак, как мне получить доступ к компонентам скрипта внутри шаблона без копирования всех переменных скрипта в window?
Комментарии:
1. Вы можете создать свой индексный файл на php и включить или потребовать компоненты оттуда.
2. Я не использую php.
Ответ №1:
Как вы выяснили <script>
, внутри a <template>
выполняется в глобальной области
Если вы используете Angular, обратите внимание, что Angular прямо удаляет все <script>
содержимое из шаблонов.
Одним из обходных путей является добавление элемента HTML, который запускает код в области элемента.
<img src onerror="[CODE]">
является наиболее вероятным кандидатом:
Затем это может вызвать глобальную функцию или выполняться this.getRootNode().host
немедленно.
<template id=scriptContainer>
<script>
console.log("script runs in Global scope!!");
function GlobalFunction(scope, marker) {
scope = scope.getRootNode().host || scope;
console.log('run', marker, 'scope:', scope);
scope.elementMethod amp;amp; scope.elementMethod();
}
</script>
<img src onerror="(()=>{
this.onerror = null;// prevent endless loop if function generates an error
GlobalFunction(this,'fromIMGonerror');
})()">
</template>
<my-element id=ONE></my-element>
<my-element id=TWO></my-element>
<script>
console.log('START SCRIPT');
customElements.define('my-element',
class extends HTMLElement {
connectedCallback() {
console.log('connectedCallback', this.id);
this.attachShadow({ mode: 'open' })
.append(scriptContainer.content.cloneNode(true));
}
});
</script>
Более подробная игровая площадка, включая внедрение скриптов, на: https://jsfiddle.net/WebComponents/q0k8ts6b /
Ответ №2:
Вот решение,
my-component:
<template>
<div id="something"></div>
<script>
makeComponent.getComponent("my-component", "something").innerHTML = "Something"
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
this.setAttribute("id", name);
shadow.appendChild(document.importNode(template.content, true));
}
}
return customElements.define(name, UnityComponent);
}
function getComponent(host, query) {
return document.getElementById(host).shadowRoot.querySelector(query);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { getComponent, loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
Однако я думаю, что это не лучший способ, возможно, мне нужно использовать события здесь и передать теневую область прослушивателю, который вызывается в теге script в шаблоне, но я пока не знаю, как передать область событию.
Вверх:
С помощью событий:
my-component:
<template>
<div id="something"></div>
<script>
document.addEventListener("custom-event", (e) => {
console.log(e.detail.target.shadowRoot.getElementById("date-year"));
})
</script>
</template>
index.js:
window.makeComponent = (function () {
function fetchAndParse(url) {
return fetch(url, { mode: "no-cors" })
.then((res) => res.text())
.then((html) => {
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const head = document.head;
const template = head.querySelector("template");
return template;
});
}
function defineComponent(name, template) {
class UnityComponent extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(document.importNode(template.content, true));
const event = new CustomEvent("custom-event", {'detail': {
target: this
}});
document.dispatchEvent(event);
}
}
return customElements.define(name, UnityComponent);
}
function loadComponent(name, url) {
fetchAndParse(url).then((template) => defineComponent(name, template));
}
return { loadComponent };
})();
makeComponent.loadComponent("my-component", "my-component.html");
Тем не менее, я предпочитаю даже первое решение. Но если вам нужны вложенные компоненты, первый не работает, вам нужен второй.