Как я получу доступ к компонентам скрипта внутри шаблона?

#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");

  

Тем не менее, я предпочитаю даже первое решение. Но если вам нужны вложенные компоненты, первый не работает, вам нужен второй.