#javascript #html #web-component #shadow-dom
#javascript #HTML #веб-компонент #shadow-dom
Вопрос:
Новичок в веб-компонентах и веб-технологиях в целом, я следую этому интересному руководству https://ayushgp.github.io/html-web-components-using-vanilla-js-part-2 /. В руководстве используются директивы импорта для импорта шаблонов в виде отдельных файлов. Этот способ импорта больше не поддерживается. Я пытаюсь исправить это, используя объяснение автора, приведенное в части 1 (обновление) в его руководстве, но это не работает.
На основе репозитория git, предоставленного в руководстве https://github.com/ayushgp/web-components-tutorial , вот мои изменения:
./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-шаблонов в виде отдельных файлов?
Спасибо!
Комментарии:
1. Вместо
git diff
этого, не могли бы вы, пожалуйста, поделиться своим кодом из обоих файлов в отдельном блоке кода?2. Что
currentDocument
должно быть? Я не знаю о таком свойстве. Вероятно, должно быть простоdocument.createElement
?3. Да, вы правы, спасибо! Изменение currentDocument на document устраняет проблему в Chrome. в firefox возникает эта ошибка
4. «Ошибка типа: PersonDetail.updatePersonDetails не является функцией _attachEventListener /components/PeopleController/PeopleController.js:30 _fetchAndPopulateData /components/PeopleController/PeopleController.js:20 обещание обратного вызова *_fetchAndPopulateData /components/PeopleController/PeopleController.js: 15 connectedCallback /components/PeopleController/PeopleController.js:55 <анонимный> components/PeopleController/PeopleController.js:59 async* /components/PeopleController/PeopleController.js:60«
Ответ №1:
Просто вы можете импортировать HTML-файлы с помощью webpack setup.
импортируйте html из ‘./header.html ‘
Для получения более подробной информации посетите: https://roshan-khandelwal.medium.com/web-components-c7aef23fe478