首页 > 解决方案 > Web组件html模板导入表单分隔文件

问题描述

一般来说,Web 组件和 Web 技术的新手,我正在关注这个有趣的教程https://ayushgp.github.io/html-web-components-using-vanilla-js-part-2/。本教程使用导入指令将模板作为单独的文件导入。不再支持这种导入方式。我正在尝试使用他教程中第 1 部分(更新)中提供的作者解释来解决这个问题,但它不起作用。

基于教程https://github.com/ayushgp/web-components-tutorial提供的 git repo ,这里是我的更改:

./index.html

<html>

<head>
  <meta charset="UTF-8" />
  <title>Web Component Part 2</title>
</head>

<body>
  <people-controller></people-controller>
  <script src="./components/PeopleController/PeopleController.js"></script>
</body>

</html>

./components/PeopleController/PeopleController.html

<template id="people-controller-template">
  <link rel="stylesheet" href="/components/PeopleController/PeopleController.css">
  <people-list id="people-list"></people-list>
  <person-detail id="person-detail"></person-detail>
  <script src="./components/PeopleController/PeopleList/PeopleList.js"></script>
  <script src="/components/PeopleController/PersonDetail/PersonDetail.js"></script>
</template>

./components/PeopleController/PeopleController.js

(async () => {
  const res = await fetch('/components/PeopleController/PeopleController.html');
  const textTemplate = await res.text();

  // Parse and select the template tag here instead
  // of adding it using innerHTML to avoid repeated parsing
  // and searching whenever a new instance of the component is added.
  const HTMLTemplate = new DOMParser().parseFromString(textTemplate, 'text/html')
    .querySelector('template');

  function _fetchAndPopulateData(self) {
    let peopleList = self.shadowRoot.querySelector('#people-list');
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then((response) => response.text())
      .then((responseText) => {
        const list = JSON.parse(responseText);
        self.peopleList = list;
        peopleList.list = list;

        _attachEventListener(self);
      })
      .catch((error) => {
        console.error(error);
      });
  }
  function _attachEventListener(self) {
    let personDetail = self.shadowRoot.querySelector('#person-detail');

    //Initialize with person with id 1:
    personDetail.updatePersonDetails(self.peopleList[0]);

    self.shadowRoot.addEventListener('PersonClicked', (e) => {
      // e contains the id of person that was clicked.
      // We'll find him using this id in the self.people list:
      self.peopleList.forEach(person => {
        if (person.id == e.detail.personId) {
          // Update the personDetail component to reflect the click
          personDetail.updatePersonDetails(person);
        }
      })
    })
  }

  class PeopleController extends HTMLElement {
    constructor() {
      super();
      this.peopleList = [];
    }

    connectedCallback() {
      const shadowRoot = this.attachShadow({ mode: 'open' });
      const instance = HTMLTemplate.content.cloneNode(true);
      shadowRoot.appendChild(instance);

      _fetchAndPopulateData(this);
    }
  }

  customElements.define('people-controller', PeopleController);
})()

./components/PeopleController/PeopleList/PeopleList.html

<template id="people-list-template">
  <style>
    .people-list__container {
      border: 1px solid black;
    }

    .people-list__list {
      list-style: none
    }

    .people-list__name {
      cursor: pointer;
    }

    .people-list__list>li {
      font-size: 20px;
      font-family: Helvetica;
      color: #000000;
      text-decoration: none;
    }
  </style>
  <div class="people-list__container">
    <ul class="people-list__list"></ul>
  </div>
</template>

./components/PeopleController/PeopleList/PeopleList.js

(async () => {
  const res = await fetch('/components/PeopleController/PeopleList/PeopleList.html');
  const textTemplate = await res.text();

  // Parse and select the template tag here instead
  // of adding it using innerHTML to avoid repeated parsing
  // and searching whenever a new instance of the component is added.
  const HTMLTemplate = new DOMParser().parseFromString(textTemplate, 'text/html')
    .querySelector('template');

  function _createPersonListElement(self, person) {
    let li = currentDocument.createElement('LI');
    li.innerHTML = person.name;
    li.className = 'people-list__name'
    li.onclick = () => {
      let event = new CustomEvent("PersonClicked", {
        detail: {
          personId: person.id
        },
        bubbles: true
      });
      self.dispatchEvent(event);
    }
    return li;
  }

  class PeopleList extends HTMLElement {
    constructor() {
      // If you define a constructor, always call super() first as it is required by the CE spec.
      super();

      // A private property that we'll use to keep track of list
      let _list = [];

      // Use defineProperty to define a prop on this object, ie, the component.
      // Whenever list is set, call render. This way when the parent component sets some data
      // on the child object, we can automatically update the child.
      Object.defineProperty(this, 'list', {
        get: () => _list,
        set: (list) => {
          _list = list;
          this.render();
        }
      });
    }

    connectedCallback() {
      // Create a Shadow DOM using our template
      const shadowRoot = this.attachShadow({ mode: 'open' });
      const instance = HTMLTemplate.content.cloneNode(true);
      shadowRoot.appendChild(instance);
    }

    render() {
      let ulElement = this.shadowRoot.querySelector('.people-list__list');
      ulElement.innerHTML = '';

      this.list.forEach(person => {
        let li = _createPersonListElement(this, person);
        ulElement.appendChild(li);
      });
    }
  }

  customElements.define('people-list', PeopleList);
})();

./components/PeopleController/PersonDetail/PersonDetail.html

<template id="person-detail-template">
  <link rel="stylesheet" href="/components/PeopleController/PersonDetail/PersonDetail.css">
  <div class="card__user-card-container">
    <h2 class="card__name">
      <span class="card__full-name"></span> (
      <span class="card__user-name"></span>)
    </h2>
    <p>Website: <a class="card__website"></a></p>
    <div class="card__hidden-content">
      <p class="card__address"></p>
    </div>
    <button class="card__details-btn">More Details</button>
  </div>
</template>

./components/PeopleController/PersonDetail/PersonDetail.js

(async () => {
  const res = await fetch('/components/PeopleController/PersonDetail/PersonDetail.html');
  const textTemplate = await res.text();

  // Parse and select the template tag here instead
  // of adding it using innerHTML to avoid repeated parsing
  // and searching whenever a new instance of the component is added.
  const HTMLTemplate = new DOMParser().parseFromString(textTemplate, 'text/html')
    .querySelector('template');

  class PersonDetail extends HTMLElement {
    constructor() {
      // If you define a constructor, always call super() first as it is required by the CE spec.
      super();

      // Setup a click listener on <user-card>
      this.addEventListener('click', e => {
        this.toggleCard();
      });
    }

    // Called when element is inserted in DOM
    connectedCallback() {
      const shadowRoot = this.attachShadow({ mode: 'open' });
      const instance = HTMLTemplate.content.cloneNode(true);
      shadowRoot.appendChild(instance);
    }

    // Creating an API function so that other components can use this to populate this component
    updatePersonDetails(userData) {
      this.render(userData);
    }

    // Function to populate the card(Can be made private)
    render(userData) {
      this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
      this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
      this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
      this.shadowRoot.querySelector('.card__address').innerHTML = `<h4>Address</h4>
      ${userData.address.suite}, <br />
      ${userData.address.street},<br />
      ${userData.address.city},<br />
      Zipcode: ${userData.address.zipcode}`
    }

    toggleCard() {
      let elem = this.shadowRoot.querySelector('.card__hidden-content');
      let btn = this.shadowRoot.querySelector('.card__details-btn');
      btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details';
      elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
    }
  }

  customElements.define('person-detail', PersonDetail);
})();

以及我在使用 chrome 时遇到的错误

PeopleController.js:23 ReferenceError: currentDocument is not defined
    at _createPersonListElement (PeopleList.js:12)
    at PeopleList.js:59
    at Array.forEach (<anonymous>)
    at HTMLElement.render (PeopleList.js:58)
    at HTMLElement.set (PeopleList.js:42)
    at PeopleController.js:18

我该如何解决?

有没有比作者提供的更好的方法来将 html 模板作为单独的文件导入?

谢谢!

标签: javascripthtmlweb-componentshadow-dom

解决方案


只需您可以使用 webpack 设置导入 HTML 文件。

从'./header.html'导入html

欲了解更多详情,请访问:https ://roshan-khandelwal.medium.com/web-components-c7aef23fe478


推荐阅读