Search code examples
javascripthtmlcustom-element

Can life cycle callbacks in an HTML5 custom element be defined after registration?


I have been working with HTML5 custom elements lately, and stumbled upon a frustrating issue. Considering the following HTML body in all examples:

<body>
    <h2>Foo elements.</h2>
    <foo-element>One!</foo-element>
    <foo-element>Two!</foo-element>
</body>

And this script attached to the head of the page for registering the custom element:

var HTMLFooElement = document.registerElement('foo-element', {
  prototype: Object.create(HTMLDivElement.prototype, {
    createdCallback: { value: function() {
      console.log('[CALLBACK] created: ', this);
    }},
    attachedCallback: { value: function() {
      console.log('[CALLBACK] attached: ', this);
    }}
  })
});

The application will work as expected on Chromium version 41.0.2272.76: the custom elements declared will trigger both callbacks, printing 4 lines to the console.

[CALLBACK] created: <foo-element>One!</foo-element> [CALLBACK] attached: <foo-element>One!</foo-element> [CALLBACK] created: <foo-element>Two!</foo-element> [CALLBACK] attached: <foo-element>Two!</foo-element>

But now I had this use case where I have to postpone the definition of attachedCallback to a future occasion:

var HTMLFooElement = document.registerElement('foo-element', {
  prototype: Object.create(HTMLDivElement.prototype, {
    createdCallback: { value: function() {
      console.log('[CALLBACK] created: ', this);
    }},
  })
});
HTMLFooElement.prototype.attachedCallback = function() {
  console.log('[CALLBACK] attached: ', this);
};

It turns out that this version will not trigger attachedCallback and no "attached" log lines are printed (jsfiddle). The function would certainly be in all my custom elements as a member at this point, since all code was executed before the body part of the DOM was processed. And regardless of that, the result is the same when creating and appending new elements in JavaScript after the document is loaded.

It baffles me that late definitions of a life cycle callback after the custom tag was registered are not being applied. What should I do to define a life cycle callback after calling registerElement and actually make it effective?


Solution

  • The procedure of registering an element seems to copy each life cycle callback internally, which means that modifying the original prototype poses no effect. I managed to avoid redefinition in my project, but to those stumbling upon the same issue, introducing a proxy is one way to solve it that does not involve re-registering the element.

    var protoProxy = {
        createdCallback: function(e) {
            console.log('[CALLBACK] created: ', e);
        },
        attachedCallback: function(e) {
          console.log('[CALLBACK] placeholding ...');
        }
    };
    var HTMLFooProto = Object.create(HTMLDivElement.prototype, {
      createdCallback: { value: function() {
          protoProxy.createdCallback(this);
      }},
      attachedCallback: { value: function() {
          protoProxy.attachedCallback(this);
        }}
    });
    
    var HTMLFooElement = document.registerElement('foo-element', {
      prototype: HTMLFooProto
    });
    
    protoProxy.attachedCallback = function(e) {
      console.log('[CALLBACK] attached: ', e);
    };
    

    jsFiddle