Home > Software design >  How could I create an element wrapper in Javascript?
How could I create an element wrapper in Javascript?

Time:04-01

For pure educational and curiosity purposes, I am trying to create an element-wrapper object that allows me to tack-on my own properties and methods to an element. The behavior I'm trying to simulate is basically this:

// get a button element to wrap
const button = document.querySelector('button');

// some function that wraps new properties/methods around a given element
function wrap(element) {
    this.customName = 'John';
    this.customAge = 100;

    this.printName = function() {
        console.log(this.customName);
    }

    // ...
    // ...somehow inherit element fields...
    // ...
}

// wrap the button element
const customElement = new wrap(button);

// custom behavior:
console.log(customElement.customAge) // output => 100
customElement.printName() // output => 'John'

// legacy behavior
console.log(customElement.clientHeight) // output => client height
customElement.remove() // => should still call 'remove' on the element

So, here I should be able to add my own methods/properties but still access the original fields normally. Is this even possible?

I'm using a constructor function here as an example just to demonstrate the intended behavior, but I don't actually know if this would be relevant for the solution. I'm new to Javascript and I have done a ton of research on prototypes and classes, but I'm still confused on what approach I would take here.

Edit: As Brad pointed out in the comments, I also tried this implementation using classes:

class MyButton extends HTMLButtonElement {
    constructor() {
        super();
        this.customName = 'John';
        this.customAge = 100;
    }

    printName() {
        console.log(this.customName);
    }
}

const myBtn = new MyButton();

But this resulted in the error: Uncaught TypeError: Illegal constructor

CodePudding user response:

I haven't test this, but maybe something like this:

// get a button element to wrap
const button = document.querySelector('button');

// some function that wraps new properties/methods around a given element
function wrap(element) {
    Object.defineProperties(element, {
        customName: {value:"John"},
        customAge: {value:100},
        printName:{value: () => console.log(element.customName)}
    })
    return element
}

// wrap the button element
const customElement = wrap(button);

// custom behavior:
console.log(customElement.customAge) // output => 100
customElement.printName() // output => 'John'

// legacy behavior
console.log(customElement.clientHeight) // output => client height
customElement.remove() // => should still call 'remove' on the element
<button>Hello world!</button>

CodePudding user response:

Another method could be used is wrap element into proxy() This will allow return custom data if property doesn't exist and send notifications when properties changed:

const customElement = function (element, properties = {})
{
  this.element = element;
  this.customName = 'John';
  this.customAge = 100;

  this.printName = function() {
      console.log(this.customName);
  }

  //override default properties
  for(let i in properties)
  {
    if (i in element)
      element[i] = properties[i];
    else
      this[i] = properties[i];
  }

  return new Proxy(this, {
    get(target, prop)
    {
      if (prop in target.element) //is property exists in element?
      {
        if (target.element[prop] instanceof Function)
          return target.element[prop].bind(target.element);

        return target.element[prop];
      }
      else if (prop in target) //is property exists in our object?
        return target[prop];
      else
        return "unknown property"; //unknown property
    },
    set(target, prop, value, thisProxy)
    {
      const oldValue = thisProxy[prop];

      if (prop in target.element)
        target.element[prop] = value;
      else
        target[prop] = value;
      // send notification
      target.element.dispatchEvent(new CustomEvent("propertyChanged", {
        detail: {
          prop,
          oldValue,
          value
        }
      }));
    }
  });
}


const button = new customElement(document.createElement("button"), {customName: "Not John"});


button.addEventListener("propertyChanged", e =>
{
  console.log("property changed", e.detail);
});


button.printName();
console.log("age:", button.customAge);
console.log("height:", button.clientHeight);

console.log("blah:", button.blah);

button.blah = "ok";
console.log("blah:", button.blah);

  • Related