Изменение класса пользовательского элемента после его определения

Можно ли изменить класс пользовательского элемента/веб-компонента после его регистрации? Это не то, что я обычно (или когда-либо) хочу делать в рабочем коде, но при создании прототипа или реализации инструмента исправления кода для разработки это было бы полезно. Вот что я пробовал, безуспешно, до сих пор:

document.body.appendChild(document.createElement('foo-bar')) // empty element

class FooBar extends HTMLElement {
  connectedCallback () {
    this.textContent = 'foo bar'
  }
}

customElements.define('foo-bar', FooBar) // element shows "foo bar"

FooBar.protoype.connectedCallback = function () {
  this.textContent = 'foo bar 2'
}

document.body.appendChild(document.createElement('foo-bar')) // element shows "foo bar", not "foo bar 2"

customElements.get('foo-bar').protoype.connectedCallback = function () {
  this.textContent = 'foo bar 2'
}

document.body.appendChild(document.createElement('foo-bar')) // element shows "foo bar", not "foo bar 2"

class FooBar2 extends HTMLElement {
  connectedCallback () {
    this.textContent = 'foo bar 2'
  }
}

customElements.define('foo-bar', FooBar2) // Exception thrown, 'foo-bar' already defined

Подводя итог, можно сказать, что изменение класса, первоначально переданного в CustomElementsRegistry, не имеет никакого эффекта. Назначение прототипу того, что находится в реестре, не имеет никакого эффекта. Попытка присвоить новый класс элементу в реестре невозможна, так как возникает исключение. Все эти три варианта поведения противоречат моему опыту почти со всеми аспектами любого другого JavaScript API. Крайне необычно, к лучшему или к худшему, невозможность что-то видоизменить, если оно не настроено как ненастраиваемое. Однако, когда я проверяю, настраиваются ли объекты, среда выполнения говорит, что да!

Кто-нибудь знает способ изменить/дополнить определение пользовательского элемента после его определения?


person james_womack    schedule 14.12.2017    source источник
comment
Кажется, что customElements.define получает ссылку на различные функции-члены предоставленного класса, и попытка переопределить их у меня не сработала.   -  person Intervalia    schedule 19.12.2017


Ответы (4)


Невозможно изменить тег после его определения, кажется, что он извлекает класс при определении.

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

в основном вам нужно полагаться на следующие части

  1. зная, какие элементы не определены, в идеале вызываться всякий раз, когда вы вносите изменения в DOM, в качестве альтернативы вы можете поместить setInterval для разработки. document.querySelectorAll(':not(:defined)')

  2. определите встроенный пользовательский элемент, чтобы вы могли использовать один и тот же конструктор для ВСЕХ пользовательских элементов. customElements.define(elementName, class extends HTMLElement {...


document.querySelectorAll(':not(:defined)').forEach((ele, k) => {
        let elementName = ele.tagName.toLowerCase();
        if (!customElements.get(elementName)) {

            //defining custom element
            customElements.define(elementName, class extends HTMLElement {
                constructor() {
                    super();
                }

                disconnectedCallback() {
                    if (this._elementClass) {
                        this._elementClass.disconnectedCallback()
                    }
                }

                connectedCallback() {

                    let element = (this.tagName).toLowerCase();

                    //reload the class variable here eg: importScript/getScripts 
                    //dependent on whatever variable you want eg: cache id or 
                    //then make calls to the customElement class
                    this._elementClass = new NodeTest(this);
                    this._elementClass.connectedCallback(); 


...


var NodeTest = class {

    constructor(ele) {
        this.ele = ele;
    }

    disconnectedCallback() {

    }

    connectedCallback() {

        this.render();
    }

    render() {
        this.ele.innerHTML = '<h1>hheee</h1>';
    }
}
person Onyx    schedule 13.06.2019
comment
Я реализовал похожий подход, но явно не такой, как вы сделал, но неявно с помощью monkey-patch window.customElements. Таким образом, по крайней мере, теоретически он мог бы работать с любым CustomElement (хотя сейчас есть много ограничений). - person Christian Ulbrich; 06.02.2020

Я думаю, что было бы сложно изменить поведение тега, они (разработчик браузера) могут подумать, что один тег должен быть определен только один раз.

Я пробовал изменить теневой корень, это сработало. Если вы хотите создать шаблон для своего пользовательского тега, теневой корень поможет.

let customTable = document.registerElement('custom-table', {
  prototype: Object.create(HTMLElement.prototype, {
    createdCallback: {
      value: function() {
        let shadowRoot = this.createShadowRoot();
        window.shadowRoot = shadowRoot
        let template = document.querySelector('#custom-table');
        shadowRoot.appendChild(template.content.cloneNode(true));
      }
    }
  })
});

let i = 0
setInterval(() => {
  shadowRoot.append(`${i++}s past `)
}, 1000)
table {
  height: 100px;
  width: 100px;
  background-color: black;
}
<template id="custom-table">
  <style>
    table {
    }
  </style>
  <table>
    <thead>
      <tr>
        <td>1</td>
        <td>2</td>
        <td>3</td>
        <td>4</td>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>P</td>
        <td>P</td>
        <td>A</td>
        <td>P</td>
      </tr>
    </tbody>
  </table>
</template>
<custom-table></custom-table>
<table>
</table>

Использование MVVM - еще один вариант, они также могут создавать шаблоны, но для модификации в реальном времени может потребоваться ручной запуск повторного рендеринга.

  • angular (машинопись, нужно скомпилировать)
  • реагировать (jsx, нужно скомпилировать)
  • угловой 1.x
  • вью
person Josh Lin    schedule 14.12.2017
comment
Эй, Джош. Спасибо за ваш ответ. Я спрашиваю, как изменить или заменить определение класса/пользовательского элемента за тегом. Не как изменить DOM внутри HTML тега на странице. Я всегда могу определить тег без теневого DOM или с открытым теневым DOM, если я хочу изменить сам HTML. Если вы посмотрите на мои комментарии в коде моего вопроса, я просто хочу иметь возможность делать то, что я там говорю. Хотя еще раз спасибо за ответ :) - person james_womack; 14.12.2017
comment
@james_womack, как вы уже знаете, наш код запускается в браузере, хотя стандарт W3C существует, тем не менее, разработчик браузера решает, каким будет результат. Привязка к другому тегу будет работать, но я уверен, что это не то, что вам нужно, поэтому я думаю, вы хотите что-то шаблонизировать, и шаблон изменится, когда вы этого захотите :) - person Josh Lin; 14.12.2017

Я реализовал очень грубый прототип, который позволяет вам customElements.define() использовать CustomElements несколько раз. Это дает вам возможность перезаписывать любое использование вашего пользовательского элемента в будущем.

Изменение существующего класса для CustomElement имеет тот недостаток, что вы потеряете сопоставление между фактическим CustomElement и его фактическим классом, и это может быть причиной, почему это не так. возможный...

Загрузив описанную выше реализацию прототипа, вы можете сделать следующее:

// add <foo-bar> to the DOM
// define it once
customElements.define('foo-bar', class extends HTMLElement {
  constructor() { super() };
  connectedCallback() { this.textContent = 'foo bar'; }
}
// ...
// and define it again :)
customElements.define('foo-bar', class extends HTMLElement {
  constructor() { super() };
  connectedCallback() { this.textContent = 'foo bar 2'; }
}
// and add it <foo-bar> again to the DOM to get the new impl
person Christian Ulbrich    schedule 06.02.2020
comment
Вы оборачиваете customElements и делегируете ему? - person james_womack; 17.03.2020
comment
@james_womack Вкратце да, однако фактическая хитрость заключается в том, что я генерирую CustomElements с увеличивающимся индексом, и они определяются через обычный API CE. Сначала создается компонент wrapper, использующий текущий индекс через замыкание. Просто взгляните на источник, может быть, сами, он открыт. :) - person Christian Ulbrich; 18.03.2020

Мне это нужно для горячей перезагрузки, у меня есть решение, но оно использует clojurescript. В конце код скомпилирован в javascript, так что, возможно, стоит взглянуть: https://clojureverse.org/t/hot-reloading-custom-elements-web-components/5964

person Jp_    schedule 14.05.2020
comment
Горячая перезагрузка - причина, по которой я тоже хотел это сделать - person james_womack; 31.10.2020