Cannot access attributes of a custom element from its constructor
Asked Answered
G

2

8

I'm trying to create a polyfill of sorts using the Custom Elements API for custom elements used by an in-game browser engine to display buttons and similar. However, I can't seem to access the element's attributes (eg. src, href ...) from within the constructor.

Here is an example:

class KWButton extends HTMLElement {
  constructor() {
    super();
    var attributes = this.attributes;
    var shadow = this.attachShadow({
      mode: 'open'
    });
    var img = document.createElement('img');
    img.alt = this.getAttribute('text'); // the getAttribute call returns null
    img.src = this.getAttribute('src'); // as does this one
    img.width = this.getAttribute('width'); // and this
    img.height = this.getAttribute('height'); // and this
    img.className = 'vivacity-kwbutton'; // this works fine
    shadow.appendChild(img);
    img.addEventListener('click', () => {
      window.location = this.getAttribute('href'); // this works perfectly fine
    });
  }
}
customElements.define('kw-button',
  KWButton);
<kw-button src="https://placekitten.com/g/198/39" width="198" height="39" icon_src="https://placekitten.com/g/34/32" icon_width="34" icon_height="32" href="https://placekitten.com/" text="placekiten" color="#ffffff" size="18"></kw-button>
Gulch answered 15/2, 2017 at 13:44 Comment(2)
This seems to work fine as far as I can tell. Note that it's probably wise to delay work (like creating and loading an image) for the connectedCallback lifecycle event.Gnarled
@Gnarled This is quite odd; it doesn't seem to run on my page (vivacity.fs3d.net)Gulch
B
18

You cannot access the element DOM tree with querySelector() and appendChild(), and attributes with getAttribute() and setAttribute() in the constructor().

It's because at the time constructor() is called the custom element has no content yet.

You should defer that in the connectedCallback() method and it will be fine.

From the specs:

The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods.

In general, work should be deferred to connectedCallback as much as possible

Ballerina answered 15/2, 2017 at 13:59 Comment(1)
Thanks for quoting the specs! That "It's because at the time constructor() is called the custom element has no content yet" part, however, reads a bit misleading to me, though: "content" usually means the HTML content, when talking about DOM elements. (Whereas the issue here is, AFAIK, that the object's DOM-related state is not initialized, since it's not inserted into the DOM tree yet.)Killarney
P
-1

Update as of 12me21 correction:

  • if the element was registered / defined before being parsed in HTML, the attributes won't be available
  • if the order of those is otherwise (parsed HTML as unknown, then define / upgrade) - they will

All in all I'd say now that since this is indeterminate to rely on this, well, exactly as it is warned by spec, I'd definitely refrain from accessing anything in the custom element c~tor, beside class' own definitions and alike.


Although I swear I've seen that spec once (the one @Supersharp mentioned), but nowadays:

  • inspection of the attributes is allowed (works for me on Chrome, Firefox and Safari), so getAttribute is OKAY
  • mutation of the attributes is, as expected, forbidden

Well, probably we indeed should read that 'gain' as specifically referring to mutation.

One could say - wait, but if element can't gain any attribute - obviously there is nothing to inspect. Well, the following snippet works for me (on any browser):

class A extends HTMLElement {
  constructor() {
    super();
    console.log(this.getAttribute('data-some'));
  }
}

globalThis.customElements.define('x-a', A);

const e = document.createElement('x-a');
// console: null

const t = document.createElement('div');
t.innerHTML = '<x-a data-some="test"></x-a>';
// console: test

CodePen

Psychopathy answered 14/2, 2021 at 17:24 Comment(5)
This doesn't work if <x-a data-some="test"></x-a> is defined directly in the htmlEmbow
I'm not really clear on 'is defined directly in the HTML', do you mean you are missing the 'customElements.define' code? If yes - this is obvious, the element is actually not defined at all.Psychopathy
Also please see updated CodePen where I've added the element directly to HTML first, still works as I've described.Psychopathy
oh sorry, that wasn't a complete explanation. If the element exists in the html file, and the script executes /before/ that element is parsed, the constructor will run immediately and the attributes won't be accessible yet: jsfiddle.net/L2hj7eko (this happens with both inline scripts and external scripts)Embow
Yep, in case the registration of the element happened before the element's parse phase - it'll be missing attributes. I've not taken this case into consideration - updating my post above to be corrected, thanks!Psychopathy

© 2022 - 2024 — McMap. All rights reserved.