Как динамически скрывать и показывать содержимое слота в веб-компоненте

Как динамически скрывать и показывать содержимое слота в веб-компоненте

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 градуса. Потому что… Луизиана.


Также опубликовано здесь.< /а>


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE