Search code examples
javascripthtmlpolyfills

Have JS APIs return a polyfilled DOM node


I am using the <data> element in HTML, which has decent support, but not sufficient for my purposes. The only extra functionality in HTMLDataElement is a getter for value, which returns the respective attribute.

Of course this is trivial to implement, just use the following code (after feature detection, of course)

class HTMLDataElement extends HTMLElement {
    constructor() {
        super();
    }

    get value() {
        return this.getAttribute(`value`);
    }
}

This works great. Only one problem: when using native APIs such as getElementById, querySelector, etc., the returned Node is not of instance HTMLDataElement. How can I make it so, if this is even possible?

To be clear, I'd like to be able to do document.querySelector('foo').value, which would act the same with or without browser support for <data>.

(I'm well aware that I can just use .getAttribute('value') instead of .value. The point is I don't want to.)


Solution

  • After reaching out to Jonathan Neal* on Twitter, he provided a great example of how this can be done.

    if (!this.HTMLDataElement) {
        this.HTMLDataElement = this.HTMLUnknownElement;
    
        const valueDescriptor = Object.getOwnPropertyDescriptor(HTMLDataElement.prototype, 'value');
    
        Object.defineProperty(
            HTMLDataElement.prototype,
            'value',
            valueDescriptor || {
                get() {
                    return 'DATA' === this.nodeName && this.getAttribute('value');
                }
            }
        );
    }
    
    console.log('data:', document.querySelector('data').value);
    
    console.log('xdata:', document.querySelector('xdata').value);
    <data value="this should work">
    <xdata value="this should not work">

    * For the unfamiliar, Jonathan Neal is large contributor of PostCSS plugins, and has created many JavaScript polyfills himself.