Оборачивание поля выбора в пользовательский элемент - PullRequest
1 голос
/ 04 мая 2020

Я хочу создать пользовательский элемент html, который должен вести себя почти идентично нативному элементу <select>, но кроме того, он также должен вызывать определенную функцию обновления каждый раз, когда изменяется атрибут или дочерний узел. (история вопроса: Это необходимый шаг для использования элемента bootstrap -select внутри структуры elm. См. также мой последний вопрос .)

Использование В фреймворке LitElement мне удалось собрать работающий пользовательский элемент (называемый <lit-select>), аналогичный описанному выше. Но, к сожалению, я не смог заставить его принимать элементы html <option> или <optgroup> как дочерние. Вместо этого пользователь должен передать список параметров определенному атрибуту в виде строки, закодированной в json.

То есть вместо вызова

<lit-select>
  <option>foo</option>
  <option>bar</option>
</lit-select>

пользователь должен вызвать

<lit-select items='["foo", "bar"]'></lit-select>

Каким образом мне нужно изменить определение <lit-select>, чтобы сделать возможным первый вызов? Мне известен элемент <slot>, но, к сожалению, этот элемент не разрешен внутри <select>, поэтому браузер просто удаляет его.

Заранее спасибо!


Обновление 1

На самом деле существуют некоторые ограничения, делающие проблему более сложной, чем я думал поначалу:

  • Я должен избегать теневого DOM. Это потому, что мой пользовательский элемент получает стиль / улучшается с помощью bootstrap (и bootstrap -select) css / js, которые смотрят только на обычный DOM. Как я только что узнал, это исключает slot элементы, поскольку они являются теневыми DOM-спецификациями c.
  • Мой пользовательский элемент должен полностью реагировать на изменения (добавление / удаление дочерних заметок, изменение атрибутов). Это потому, что я планирую использовать элемент внутри виртуального DOM (в моем случае это elm, но он также должен работать с реагировать).

Приложение

Мое определение <lit-select>:

import { LitElement, html, customElement, property } from 'lit-element';
import * as $ from 'jquery';
import 'bootstrap';
import 'bootstrap-select';

@customElement('lit-select')
export class LitSelect extends LitElement {

    @property({ type : Array }) items = []

    updated() {
        $(this).find(".selectpicker").selectpicker('refresh');
    }

    createRenderRoot() {
        return this;
    }

    private renderItem(item: string) {
        return html`
            <option>
                ${item}
            </option>
        `;
    }

    render() {
        return html`
            <select class="selectpicker" data-live-search = "true">
                ${this.items.map(item => this.renderItem(item))}
            </select>
        `;
    }
}

Ответы [ 2 ]

0 голосов
/ 04 мая 2020

Как насчет этого решения?

import {LitElement, html } from 'lit-element';


export class GraniteSelect extends LitElement {

  static get properties() {
    return {
      options: {
        type: Object,
      }
    }
  }

  constructor() {
    super();
    this.options =  [];
  }

  connectedCallback() {
    super.connectedCallback();

    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        mutation.removedNodes.forEach((node) => {
          if (node.nodeName === 'OPTION' ) {
            this.options = [ ...this.options, node ];
           console.log(`options - ${this.options}`);
           console.dir(this.options);
          }
        });
      });
    });
    this.observer.observe(this, {
        childList: true,
    });
  }

  firstUpdate() {
    this.observer.disconnect();
  }

  createRenderRoot() {
  /**
   * Render template without shadow DOM. Note that shadow DOM features like 
   * encapsulated CSS and slots are unavailable.
   */
    return this;
  }


  render() {
    console.log('Rendering', this.options);
    return html`
      <select class="selectpicker" data-live-search = "true">
          ${this.options}
      </select>
    `;
  }
}

window.customElements.define('granite-select', GraniteSelect);

Он использует light-dom (часть createShadowRoot) и помещает все дочерние элементы параметров вашего элемента в select. Дети без опций просто игнорируются, но вы можете делать с ними все, что угодно.

Полный пример вы можете увидеть в https://stackblitz.com/edit/js-14dcae

Что вы думаете о этот подход?

0 голосов
/ 04 мая 2020

Почему бы не начать с родного W3 C стандартного пользовательского элемента

Все, что вам нужно сделать, это переместить некоторые <option> элементы.

<template id=MY-SELECT>
  <h3>My Never Todo List</h3>
  <select multiple></select>
</template>

<my-select name="NTD">
  <option>Grow up</option>
  <option>Learn React</option>
  <option>Use Lit</option>
  <option>Forget W3C Custom Elements API</option>
</my-select>

<script>
  customElements.define("my-select", class extends HTMLElement {
    static get observedAttributes() {
      return ["name"]; //use one to trigger attributeChangedCallback
    }
    connectedCallback() {
      console.log('connected');
      //clone template
      this.append(document.getElementById(this.nodeName).content.cloneNode(true));
      //MOVE options inside SELECT:
      this.querySelector('select').append(...this.querySelectorAll('option'));
    }
    attributeChangedCallback() {
      setTimeout(() => this.updated(...arguments))// execute after Event Loop is done
    }
    updated(name,oldValue,newValue){
      console.log('updated',name,oldValue,newValue);
    }
  })

</script>
...