
Как динамически скрывать и показывать содержимое слота в веб-компоненте
9 января 2023 г.С наступающим Новым годом и с первой публикацией года! Не уверен, что это вещь, но это мой блог, поэтому я делаю это вещью. Последние несколько дней я снова играл с веб-компонентами, на этот раз основываясь на простой идее: могу ли я создать веб-компонент, который опирается на внешние данные, и использовать слоты для предоставления контента на различных этапах загрузки? Я имею в виду что-то вроде этого:
<get-remote-stuff>
<div slot="loading">
Please stand by, I'm loading your stuff.
</div>
<div slot="ready">
I got the remote stuff, here it is!
</div>
<div slot="error">
Sorry, something bad happened.
</div>
</get-remote-stuff>
Идея заключается в том, что компонент будет автоматически отображать и скрывать каждый слот в зависимости от состояния удаленного асинхронного процесса. Мне удалось получить пример этой работы, но я хочу прояснить, что в этом есть части, которые я не на 100% уверен, что правильно понимаю.
Как всегда, я жду ваших отзывов, поэтому напишите мне, если у вас есть какие-либо вопросы или разъяснения.
Попытка первая
Для моей первой попытки я использовал поддельный асинхронный процесс, который просто использовал setTimeout
. Сначала я написал простой HTML:
<slot-one>
<span slot="loading">Loading slot</span>
<span slot="ready">Ready slot</span>
<span slot="error">Error slot</span>
</slot-one>
Затем я создал свой веб-компонент slot-one
:
class SlotOne extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode:'open'});
const div = document.createElement('div');
div.innerHTML = `
<slot name="loading"></slot>
<slot name="ready"></slot>
<slot name="error"></slot>
`;
const style = document.createElement('style');
style.innerHTML = `
slot {
display:none;
}
`;
shadow.appendChild(div);
shadow.appendChild(style);
}
async connectedCallback() {
console.log('connected callback');
let loader = this.shadowRoot.querySelector('slot[name=loading]');
loader.style.display='inline';
let ready = this.shadowRoot.querySelector('slot[name=ready]');
setTimeout(() => {
console.log('delayed thing done');
loader.style.display='none';
ready.style.display='inline';
}, 3000);
}
}
customElements.define('slot-one', SlotOne);
Я начинаю с создания двух элементов DOM. Один отображает слоты, а другой использует CSS, чтобы скрыть их. Обратите внимание, я указываю на элемент slot
, а не на div
, который будет отображаться при загрузке компонента.
В connectedCallback
я использую querySelector
в shadowRoot
, чтобы отобразить слот загрузки и получить указатель на слот ready
.
Я выполняю свой асинхронный процесс, в данном случае это просто setTimeout
, и когда он завершается, я скрываю загрузчик и показываю состояние готовности. Казалось, это работает просто отлично, и вы можете увидеть это в действии ниже:
https://codepen.io/cfjedimaster/pen/XWBKraG?embedable=true р>
Вторая попытка
Во второй попытке я хотел сделать две вещи. Во-первых, переключитесь на «настоящий» асинхронный процесс. Я получил бесплатный ключ для Weather API. Учитывая значение местоположения и ключ, я мог бы получить отчет о погоде здесь: https://api.weatherapi.com/v1/current.json?key=${key}&q=${q}& ;aqi=нет
. Это возвращает кучу информации, но для простоты я решил просто вернуть текущую температуру. Вот функция:
async getTemperature(q,key) {
let resp = await fetch(`https://api.weatherapi.com/v1/current.json?key=${key}&q=${q}&aqi=no`);
let data = await resp.json();
return data.current.temp_f;
}
Да, я не добавлял сюда проверку ошибок, а надо бы, но так как я в отпуске, мне немного лень. (Хорошо, те из вас, кто меня знает, знают, что мне не нужно оправдание, чтобы быть ленивым. ;)
Хорошо, с этим на месте, моя цель на этот раз была довольно простой — после получения результата сделать его доступным для слота. Чтобы справиться с этим, я использовал простой переменный токен. Вот как это выглядит:
<current-temp location="70508">
<span slot="loading">
Getting temperature...
</span>
<span slot="ready">
The temperature is {temp}F.
</span>
<span slot="error">
Error slot
</span>
</current-temp>
Я использую скобки для представления переменной, и компонент должен обрабатывать замену. Я думал, что это будет тривиально, но здесь я столкнулся с кирпичной стеной. Вот JavaScript, который я использовал для работы со слотом раньше:
let ready = this.shadowRoot.querySelector('slot[name=ready]');
// stuff...
ready.style.display='inline';
Но когда я попытался записать содержимое слота, ничего не получилось. Я пробовал innerHTML
, innerText
и даже textContents
. Ничего не сработало. Затем я попробовал что-то еще:
let readyDOM = this.querySelector('*[slot=ready]');
Это соответствует любому элементу HTML с атрибутом slot
, для которого установлено значение ready
, то есть элементу DOM из HTML внутри веб-компонента. Также обратите внимание, что я не использую shadowRoot
. Итак... Я могу скрывать и показывать элементы slot
, но фактический текст/HTML находится в "настоящем" элементе с именованным слотом. Думаю, это имеет смысл. Вот полный обработчик обратного вызова connected
:
async connectedCallback() {
console.log('connected callback');
let loader = this.shadowRoot.querySelector('slot[name=loading]');
loader.style.display='inline';
let ready = this.shadowRoot.querySelector('slot[name=ready]');
let temp = await this.getTemperature(this.location, this.KEY);
loader.style.display='none';
ready.style.display='inline';
let readyDOM = this.querySelector('*[slot=ready]');
let content = readyDOM.innerText;
content = content.replace('{temp}', temp);
readyDOM.innerText = content;
}
Это работает, и я сказал, что думаю, что это имеет смысл, но я был бы рад, если бы кто-нибудь поделился некоторыми подробностями, поскольку я немного нечетко здесь. Вы можете увидеть полную демонстрацию ниже, и да, в ней отсутствует обработчик ошибок, но его нетрудно добавить.
https://codepen.io/cfjedimaster/pen/ZEjOELE?embedable=true р>
Для любопытных, прямо сейчас в моем почтовом индексе 73,9 градуса. Потому что… Луизиана.
Также опубликовано здесь.< /а>
Оригинал