Spec-Zone.ru › Web APIs

Использование шаблонов и слотов

В этой статье объясняется, как вы можете использовать элементы <template> и <slot>, чтобы создать гибкий шаблон, который затем можно использовать для заполнения теневого DOM веб-компонента.

Правда о шаблонах

Когда вам нужно многократно использовать одинаковые структуры разметки на веб-странице, имеет смысл использовать какой-то шаблон, вместо того, чтобы повторять одну и ту же структуру снова и снова. Это было возможно и раньше, но элемент HTML <template> делает это намного проще. Этот элемент и его содержимое не отображаются в DOM, но к нему всё равно можно обратиться с помощью JavaScript.

Давайте рассмотрим тривиальный быстрый пример:

<template id="custom-paragraph">
  <p>My paragraph</p>
</template>

Это не отобразится на вашей странице, пока вы не получите на него ссылку с помощью JavaScript, а затем не добавите его в DOM, используя что-то вроде следующего:

let template = document.getElementById("custom-paragraph");
let templateContent = template.content;
document.body.appendChild(templateContent);

Хотя это тривиально, вы уже можете начать понимать, как это может быть полезно.

Использование шаблонов с веб-компонентами

Шаблоны полезны сами по себе, но они работают ещё лучше с веб-компонентами. Давайте определим веб-компонент, который использует наш шаблон в качестве содержимого своего теневого DOM. Мы назовём его <my-paragraph>:

customElements.define(
  "my-paragraph",
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById("custom-paragraph");
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({ mode: "open" });
      shadowRoot.appendChild(templateContent.cloneNode(true));
    }
  },
);

Ключевой момент здесь заключается в том, что мы добавляем клон содержимого шаблона в теневой корень, созданный с помощью метода Node.cloneNode().

И поскольку мы добавляем его содержимое в теневой DOM, мы можем включить информацию об оформлении внутри шаблона в элементе <style>, который затем изолируется внутри пользовательского элемента. Это не сработает, если мы просто добавим его в стандартный DOM.

Например:

<template id="custom-paragraph">
  <style>
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
</template>

Теперь мы можем использовать его, просто добавив его в наш HTML-документ:

<my-paragraph></my-paragraph>

Добавление гибкости с помощью слотов

Пока всё хорошо, но элемент не очень гибкий. Мы можем отобразить только один фрагмент текста внутри него, что означает, что на данный момент он даже менее полезен, чем обычный абзац! Мы можем сделать так, чтобы в каждом экземпляре элемента отображался другой текст красивым декларативным способом, используя элемент <slot>.

Слоты идентифицируются по их атрибуту name, и позволяют вам определять места для подстановки в шаблоне, которые могут быть заполнены любым фрагментом разметки, который вы хотите, когда элемент используется в разметке.

Итак, если мы хотим добавить слот в наш тривиальный пример, мы можем обновить элемент абзаца в нашем шаблоне так:

<p><slot name="my-text">My default text</slot></p>

Если содержимое слота не определено при включении элемента в разметку или если браузер не поддерживает слоты, <my-paragraph> содержит просто резервное содержимое «Мой текстовый заполнитель».

Чтобы определить содержимое слота, мы включаем HTML-структуру внутри элемента <my-paragraph> с атрибутом slot, значение которого равно имени слота, который мы хотим заполнить. Как и прежде, это может быть что угодно, например:

<my-paragraph>
  <span slot="my-text">Let's have some different text!</span>
</my-paragraph>

или

<my-paragraph>
  <ul slot="my-text">
    <li>Let's have some different text!</li>
    <li>In a list!</li>
  </ul>
</my-paragraph>

Примечание: Узлы, которые можно вставить в слоты, известны как узлы Slottable; когда узел был вставлен в слот, говорят, что он slotted.

Примечание: Без имени <slot> будет заполнен всеми дочерними узлами пользовательского элемента верхнего уровня, которые не имеют атрибута slot. Это включает узлы текста.

И на этом наш тривиальный пример заканчивается. Если вы хотите с ним поиграть, вы можете найти его на GitHub (также посмотрите его живое исполнение).

Более сложный пример

Чтобы завершить статью, давайте рассмотрим что-то немного менее тривиальное.

Следующий набор фрагментов кода показывает, как использовать <slot> вместе с <template> и некоторым JavaScript, чтобы:

  • создать элемент <element-details> со именованными слотами в его теневом корне
  • спроектировать элемент <element-details> таким образом, чтобы при использовании в документах он рендерился путём объединения содержимого элемента с содержимым из его теневого корня—то есть, части содержимого элемента используются для заполнения именованных слотов в его теневом корне

Обратите внимание, что технически можно использовать элемент <slot> без элемента <template>, например, внутри обычного элемента <div>, и всё ещё воспользоваться преимуществами возможностей замены <slot> для содержимого теневого DOM, и это может действительно помочь избежать небольших трудностей с необходимостью предварительного доступа к свойству template элемента content, (и его клонирования). Однако, в целом, более практично добавлять слоты внутри элемента <template>, так как вам, скорее всего, не придётся определять шаблон на основе уже рендерного элемента.

Кроме того, даже если он ещё не отображён, назначение контейнера как шаблона должно быть более семантически ясным при использовании <template>. Кроме того, <template> может иметь элементы, добавляемые непосредственно в него, такие как <td>, которые исчезнут при добавлении в <div>.

Примечание: Вы можете найти этот полный пример на element-details (посмотрите его живое исполнение также).

Создание шаблона с некоторыми слотами

В первую очередь, мы используем элемент <slot> внутри элемента <template>, чтобы создать новый фрагмент документа «element-details-template» содержащий некоторые именованные слоты:

<template id="element-details-template">
  <style>
    details {
      font-family: "Open Sans Light", Helvetica, Arial;
    }
    .name {
      font-weight: bold;
      color: #217ac0;
      font-size: 120%;
    }
    h4 {
      margin: 10px 0 -8px 0;
    }
    h4 span {
      background: #217ac0;
      padding: 2px 6px 2px 6px;
    }
    h4 span {
      border: 1px solid #cee9f9;
      border-radius: 4px;
    }
    h4 span {
      color: white;
    }
    .attributes {
      margin-left: 22px;
      font-size: 90%;
    }
    .attributes p {
      margin-left: 16px;
      font-style: italic;
    }
  </style>
  <details>
    <summary>
      <span>
        <code class="name"
          >&lt;<slot name="element-name">NEED NAME</slot>&gt;</code
        >
        <span class="desc"
          ><slot name="description">NEED DESCRIPTION</slot></span
        >
      </span>
    </summary>
    <div class="attributes">
      <h4><span>Attributes</span></h4>
      <slot name="attributes"><p>None</p></slot>
    </div>
  </details>
  <hr />
</template>

Этот элемент <template> имеет несколько особенностей:

  • Элемент <template> имеет элемент <style> со стилями CSS, которые ограничены только фрагментом документа, который создаёт <template>.

  • Элемент <template> использует <slot> и его атрибут name, чтобы создать три именованных слота:

    • <slot name="element-name">
    • <slot name="description">
    • <slot name="attributes">
  • Элемент <template> обертывает именованные слоты в элемент <details>.

Создание нового элемента <element-details> из <template>

Далее, давайте создадим новый пользовательский элемент с именем <element-details> и используем Element.attachShadow, чтобы прикрепить к нему, как к его теневому корню, этот фрагмент документа, который мы создали с помощью нашего элемента <template> выше. Это использует точно такой же шаблон, как мы видели в нашем предыдущем тривиальном примере.

customElements.define(
  "element-details",
  class extends HTMLElement {
    constructor() {
      super();
      const template = document.getElementById(
        "element-details-template",
      ).content;
      const shadowRoot = this.attachShadow({ mode: "open" });
      shadowRoot.appendChild(template.cloneNode(true));
    }
  },
);

Использование пользовательского элемента <element-details> с именованными слотами

Теперь давайте используем этот <element-details> элемент в нашем документе:

<element-details>
  <span slot="element-name">slot</span>
  <span slot="description"
    >A placeholder inside a web component that users can fill with their own
    markup, with the effect of composing different DOM trees together.</span
  >
  <dl slot="attributes">
    <dt>name</dt>
    <dd>The name of the slot.</dd>
  </dl>
</element-details>

<element-details>
  <span slot="element-name">template</span>
  <span slot="description"
    >A mechanism for holding client- side content that is not to be rendered
    when a page is loaded but may subsequently be instantiated during runtime
    using JavaScript.</span
  >
</element-details>

Обратите внимание на следующие моменты в этом фрагменте:

  • В фрагменте есть два экземпляра <element-details> элементов, оба использующих атрибут slot для ссылки на именованные слоты "element-name" и "description" в <element-details> корневом элементе тени.
  • Только первый из этих двух <element-details> элементов ссылается на "attributes" именованный слот. Второй <element-details> элемент не содержит ссылки на "attributes" именованный слот.
  • Первый <element-details> элемент ссылается на "attributes" именованный слот, используя элемент <dl> с элементами <dt> и <dd>.

Добавление окончательного стиля

В качестве завершающего штриха мы добавим немного CSS для элементов <dl>, <dt> и <dd> в нашем документе:

dl {
  margin-left: 6px;
}
dt {
  color: #217ac0;
  font-family: Consolas, "Liberation Mono", Courier;
  font-size: 110%;
  font-weight: bold;
}
dd {
  margin-left: 16px;
}

Результат

Наконец, давайте объединим все фрагменты и посмотрим, как выглядит результат.

Обратите внимание на следующие моменты в этом результатах:

  • Несмотря на то, что экземпляры элемента <element-details> в документе не используют напрямую элемент <details>, они отображаются с помощью <details> из-за корневого элемента тени.
  • Внутри вывода <details> содержимое элементов <element-details> заполняет именованные слоты из корневого элемента тени. Другими словами, дерево DOM из элементов <element-details> объединяется с содержимым корневого элемента тени.
  • Для обоих элементов <element-details> автоматически добавляется заголовок Атрибуты из корневого элемента тени перед позицией "attributes" именованного слота.
  • Поскольку первый <element-details> имеет элемент <dl>, который явно ссылается на "attributes" именованный слот из корневого элемента тени, содержимое этого <dl> заменяет "attributes" именованный слот из корневого элемента тени.
  • Поскольку второй <element-details> не ссылается явно на "attributes" именованный слот из корневого элемента тени, его содержимое для этого именованного слота заполняется с помощью содержимого по умолчанию из корневого элемента тени.

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API