Можно ли программно размещать элементы в веб-компонентах?

Можно ли автоматически или программно размещать вложенные веб-компоненты или элементы определенного типа без указания на них атрибута slot?

Рассмотрим такую ​​структуру:

<parent-element>
  <child-element>Child 1</child-element>
  <child-element>Child 2</child-element>

  <p>Content</p>
</parent-element>

С <parent-element> имеющим Shadow DOM следующим образом:

<div id = "child-elements">
  <slot name = "child-elements">
    <child-element>Default child</child-element>
  </slot>
</div>
<div id = "content">
  <slot></slot>
</div>

Ожидаемый результат:

<parent-element>
  <#shadow-root>
    <div id = "child-elements">
      <slot name = "child-elements">
        <child-element>Child 1</child-element>
        <child-element>Child 2</child-element>
      </slot>
    </div>
    <div id = "content">
      <slot>
        <p>Content</p>
      </slot>
    </div>
</parent-element>

Другими словами, я хочу обеспечить, чтобы <child-element> были разрешены только внутри элементов <parent-element>, аналогичных элементам <td>, которые разрешены только внутри элемента <tr>. И я хочу, чтобы они были помещены в элемент <slot name = "child-elements">. Необходимость указывать атрибут slot для каждого из них, чтобы поместить их в определенный слот <parent-element>, кажется избыточной. В то же время остальная часть содержимого внутри <parent-element> должна автоматически размещаться во втором элементе <slot>.

Сначала я искал способ определить это при регистрации родительского элемента, хотя CustomElementRegistry.define() в настоящее время поддерживает только extends в качестве опции.

Потом я подумал, может быть есть функция, позволяющая ставить элементы вручную, т.е. что-то вроде childElement.slot('child-elements'), но ее вроде как нет.

Затем я попытался добиться этого программно в конструкторе родительского элемента следующим образом:

constructor() {
  super();

  this.attachShadow({mode: 'open'});
  this.shadowRoot.appendChild(template.content.cloneNode(true));

  const childElements = this.getElementsByTagName('child-element');
  const childElementSlot = this.shadowRoot.querySelector('[name = "child-elements"]');
  for (let i = 0; i < childElements.length; i++) {
    childElementSlot.appendChild(childElements[i]);
  }
}

Хотя это не перемещает дочерние элементы в <slot name = "child-elements">, поэтому все они по-прежнему размещаются во втором элементе <slot>.

Я думаю, что это может быть хорошим вопросом, но в его нынешнем виде он немного сбивает с толку, чего вы пытаетесь достичь и чего вам удалось достичь на данный момент. Пожалуйста, не могли бы вы отредактировать / переписать свой вопрос для ясности - я люблю веб-компоненты и хотел бы помочь вам в этом.

Rounin - Standing with Ukraine 23.12.2020 12:14

@Rounin, подумайте slotchange событие для вашего ответа ...

Danny '365CSI' Engelman 23.12.2020 12:40

@Rounin Я расширил свой ответ, чтобы уточнить, каков ожидаемый результат и что я пробовал до сих пор.

Sebastian Zartner 23.12.2020 12:52

@Danny'365CSI'Engelman Насколько я вижу, событие slotchange запускается для элемента <slot> только после того, как элемент был добавлен или удален из него, но я пока не понимаю, как это помогает мне помещать их в правильный слот.

Sebastian Zartner 23.12.2020 12:55

Я полагаю, вы имеете в виду childEements[i] вместо tabs[i]??? В этом случае вы больше не используете технологию SLOT, потому что теперь вы перемещаете содержимое lightDOM в shadowDOM.

Danny '365CSI' Engelman 23.12.2020 13:33

Ой! Конечно же я имел в виду childElements[i]. Я обобщил код для SO. Ну, как я уже писал, я хочу их вставить в слоты, хотя я пока не нашел подходящего способа, кроме как переместить их из Light DOM в Shadow DOM.

Sebastian Zartner 23.12.2020 13:59
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Введение в CSS
Введение в CSS
CSS является неотъемлемой частью трех основных составляющих front-end веб-разработки.
Как выровнять Div по центру?
Как выровнять Div по центру?
Чтобы выровнять элемент <div>по горизонтали и вертикали с помощью CSS, можно использовать комбинацию свойств и значений CSS. Вот несколько методов,...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Travel Booking Angular Template один из лучших Travel & Tour booking template in the world. 30+ валидированных HTML5 страниц, которые помогут...
11
6
4 185
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваш безымянный по умолчанию <slot></slot> захватит все элементы, не назначенные именованному слоту;
поэтому slotchange Событие может захватить их и заставить child-element попасть в правильный слот:

customElements.define('parent-element', class extends HTMLElement {
    constructor() {
      super().attachShadow({mode:'open'})
             .append(document.getElementById(this.nodeName).content.cloneNode(true));
      this.shadowRoot.addEventListener("slotchange", (evt) => {
        if (evt.target.name == "") {// <slot></slot> captures
          [...evt.target.assignedElements()]
            .filter(el => el.nodeName == 'CHILD-ELEMENT') //process child-elements
            .map(el => el.slot = "child-elements"); // force them to their own slot
        } else console.info(`SLOT: ${evt.target.name} got:`,evt.target.assignedNodes())
      })}});
customElements.define('child-element', class extends HTMLElement {
    connectedCallback(parent = this.closest("parent-element")) {
      // or check and force slot name here
      if (this.parentNode != parent) {
        if (parent) parent.append(this); // Child 3 !!!
        else console.error(this.innerHTML, "wants a PARENT-ELEMENT!");
      }}});
child-element { color: red; display: block; } /* style lightDOM in global CSS! */
<template id=PARENT-ELEMENT>
  <style>
    :host { display: inline-block; border: 2px solid red; }
    ::slotted(child-element) { background: lightgreen }
    div { border:3px dashed rebeccapurple }
  </style>
  <div><slot name=child-elements></slot></div>
  <slot></slot>
</template>

<parent-element>
  <child-element>Child 1</child-element>
  <child-element>Child 2</child-element>
  <b>Content</b>
  <div><child-element>Child 3 !!!</child-element></div>
</parent-element>
<child-element>Child 4 !!!</child-element>

Обратите внимание на то, что логика обработки <child-element> не является прямым потомком <parent-element>, вы, вероятно, захотите переписать это под свои нужды.

Это работает отлично. Мне просто интересно, почему вы должны реагировать на событие slotchange и не можете предварительно переместить их в <slot name = "child-elements">, то есть когда они создаются или обновляются или до того, как они будут помещены в слот.

Sebastian Zartner 23.12.2020 14:28

Да, можно обойтись без события slotchange и поставить логику на дочерний элемент: если слот не правильный, установить его

Danny '365CSI' Engelman 23.12.2020 14:30

Также connectedCallback() не принимает параметры , поэтому parent = this.closest("parent-element") должен быть определен в теле функции.

Sebastian Zartner 23.12.2020 14:30

Нет, вы можете объявить переменные там.. это допустимый JavaScript, мы не ограничены синтаксисом TypeScript.. Если компоненты должны быть небольшими, это место для предотвращения операторов LET.. экономит 4 байта. См. метод svg() в моем IconMeister все "переменные" являются "параметрами"

Danny '365CSI' Engelman 23.12.2020 14:34

Это допустимый JavaScript, и он экономит несколько байтов при передаче по сети, да, но я имел в виду, что спецификация DOM говорит, что connectedCallback() вызывается без параметров. Размещение его в списке параметров может позволить людям, читающим ваш ответ, подумать, что они могут как-то измениться parent. Так что делать это может быть хорошо, когда вы хотите минимизировать свой код, но для ясности здесь, на SO, лучше использовать здесь оператор const.

Sebastian Zartner 23.12.2020 14:50

назначение el.slot, похоже, не работает, если узел Text... есть ли способ переназначить слот текстового узла?

Michael 26.05.2022 23:31

@Майкл, задай вопрос, я не могу вставить код в комментарии

Danny '365CSI' Engelman 27.05.2022 11:05

С недавнего времени да, вы можете, используя метод assign() элементов слота . К сожалению, Safari его пока не поддерживает, но полифилл есть.

Я еще не пробовал, но похоже, что это решает проблему. Недостатком этого подхода, похоже, является то, что вы не можете смешивать автоматическое назначение слотов с ручным назначением (пока). В моем примере было бы лучше, если бы я мог определить, что все <child-element> размещаются вручную, а остальные автоматически в безымянных <slot>.

Sebastian Zartner 18.07.2022 21:11

Другие вопросы по теме