Procedural Web Components

Create Web Components without the class API for declarative code practices.

by Julien Etienne

HTML

<hello-element id="example"></hello-element>
<goodbye-element id="example2"></goodbye-element>

JavaScript

/** 
 * A procedural alternative to extending an element into a class. 
 */
const createInterface = (name, type, callbacks) => {
  const noop = () => {}

  const customFn = {
    [name]: function() {
      return Reflect.construct(true ? type.prototype.constructor : type, [], customFn[name]);
    }
  };
  const customElement = customFn[name];
  customElement.prototype = Object.create(type.prototype);


  if (callbacks.attributeChanged) {
    customElement.prototype.attributeChangedCallback = function(attr, oldValue, newValue) {
      const attributeChangedCallback = callbacks.attributeChanged || noop;
      if (typeof attributeChangedCallback === 'function')
        attributeChangedCallback(this, attr, oldValue, newValue);
    }
  } else {
    callbacks.attributeChanged = customElement.prototype.attributeChangedCallback;
  }

  if (callbacks.connected)
    customElement.prototype.connectedCallback = callbacks.connected;

  if (callbacks.disconnected)
    customElement.prototype.disconnectedCallback = callbacks.disconnected;


  if (callbacks.adopted)
    customElement.prototype.adoptedCallback = callbacks.adopted;

  if (callbacks.observedAttributes)
    Object.defineProperty(customElement, 'observedAttributes', {
      get: function() {
        return callbacks.observedAttributes();
      }
    });

  return customElement;
}


// connectedCallback
const connected = () => console.log('connectedCallback');


// disconnectedCallback
const disconnected = () => console.log('disconnectedCallback');


// observedAttributes
const observedAttributes = () => ['name'];


// attributeChangedCallback
const attributeChanged = (element, attr, oldValue, newValue) => {
  if (attr == 'name') {
    element.textContent = `Hello, ${newValue}`;
  }
}


// Create element using given callbacks
const HelloElement = createInterface('HelloElement', HTMLElement, {
  attributeChanged,
  observedAttributes,
  connected,
  disconnected
});


// Register custom element...