Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass object(or Associative Array) as value of attribute to my web-component

I am making vanilla-js web-component and need to pass object to attribute as its value, but every time it takes it as string.

I am able to pass string to attributes of my components, but know I need to pass an object.

var obj = { name: "John", age: 30, city: "New York" };

const template = document.createElement('template');

class TabelComponent extends HTMLElement {

    constructor() {
        super();
        this.attachShadow({mode: 'open'});
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

    connectedCallback() {
        console.log(this.content);
    }
    
    get content() {
        return this.getAttribute("content")
    }
}

window.customElements.define('table-component', TabelComponent);
<table-component content=obj></table-component>

My attribute "content" should take obj as object not as string. I should get { name: "John", age: 30, city: "New York" }; in console

like image 384
Hussain Shahid Avatar asked Aug 26 '19 08:08

Hussain Shahid


People also ask

How do I use Web Components in angular app?

Create a new Angular project: **Enable the use of Web Components**, by adding a schemas property inside the @NgModule config in app. module. ts , and pass it an array with the CUSTOM_ELEMENTS_SCHEMA constant. This allows Angular to understand the HTML Web Component selector, because it's a non-Angular component.

What are Web components example?

Getting Started With Web Components. Web Components are custom HTML elements such as <hello-world></hello-world> . The name must contain a dash to never clash with elements officially supported in the HTML specification. You must define an ES2015 class to control the element.


1 Answers

What you are asking for is normally the job of a framework.

It is best to pass objects into Web Components through properties and not attributes:

Property

var obj = { name: "John", age: 30, city: "New York" };

class TabelComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'}).innerHTML = '<h1>your DOM here</h1><pre id="content"></pre>';
  }

  connectedCallback() {
  }

  set content(val) {
    this._content = val;
    // render changes
    let el = this.shadowRoot.querySelector('#content');
    el.textContent = JSON.stringify(this._content,0,2);
  }
  get content() {
    return this._content;
  }
}

window.customElements.define('table-component', TabelComponent);

setTimeout(() => {
  let el = document.getElementById('mine');
  el.content = obj;
}, 10);
<table-component id="mine"></table-component>

Data URI

BUT if you really want to pass something in through an attribute then look at using the standards of Data URIs. (https://devdocs.io/http/basics_of_http/data_uris)

This is done by always using fetch to get your data and treat the content attribute like it is a URL. This allows you to load data from a URL and still allows you to directly pass in JSON data.

Your data does need to be fully JSON compliant and also needs to be URL encoded.

It is easiest to use the observedAttributes static function to define the attribute you want to watch and then attributeChangedCallback to know when the attribute changes. Be aware that the newVal will be null when the attribute is removed and you need to handle that differently.

The example below is simple and would need some error checking. But it shows how to use a Data URI to accomplish the task.

var obj = { name: "John", age: 30, city: "New York" };

class TabelComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'}).innerHTML = '<h1>your DOM here</h1><pre id="content"></pre>';
  }

  static get observedAttributes() {
    return ['content'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      if (newVal === null) {
        // Attribute was removed
        this._content = null;
        this.render();
      }
      else {
        fetch(newVal).then(
          (res) => res.json()
        ).then(
          (data) => {
            this._content = data;
            this.render();
          }
        );
      }
    }
  }  

  connectedCallback() {
  }

  render() {
    let el = this.shadowRoot.querySelector('#content');
    el.textContent = JSON.stringify(this._content,0,2);
  }

  get content() {
    return this._content;
  }
}

window.customElements.define('table-component', TabelComponent);
<table-component content="data:application/json,%7B%22name%22%3A%22John%22%2C%22age%22%3A30%2C%22city%22%3A%22New%20York%22%7D" id="mine"></table-component>

One huge advantage of doing it this way, using fetch, is that you can also supply a URL instead of using the Data URI. This allows you to get data from anywhere and still use it.

This is also the way that the various built-in tags work. Like <img src="URL or Data URI">, <audio src="URL or Data URI"> and <video src="URL or Data URI">

JSON in attribute

If you really don't want to use the full Data URI the you can trim the code down a little to this:

var obj = { name: "John", age: 30, city: "New York" };

class TabelComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'}).innerHTML = '<h1>your DOM here</h1><pre id="content"></pre>';
  }

  static get observedAttributes() {
    return ['content'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      if (newVal === null) {
        // Attribute was removed
        this._content = null;
        this.render();
      }
      else {
        this._content = JSON.parse(newVal);
        this.render();
      }
    }
  }  

  connectedCallback() {
  }

  render() {
    let el = this.shadowRoot.querySelector('#content');
    el.textContent = JSON.stringify(this._content,0,2);
  }

  get content() {
    return this._content;
  }
}

window.customElements.define('table-component', TabelComponent);
<table-component content="{&quot;name&quot;:&quot;John&quot,&quot;age&quot;:30,&quot;city&quot;:&quot;New York&quot;}" id="mine"></table-component>

You may notice that this does require you to encode the double quotes into &quot; to prevent an accidental end-of-attribute-value.

Of course you can always force your attribute to use single quotes and then not need to escape the double quotes, but you would need to escape any single quotes:

<table-component content='{"name":"John","age":30,"city":"New York"}' id="mine"></table-component>

Variable value in Attribute

What I really think you are asking for is the ability to pass the value of a variable into the Component by setting the variable name in the attribute:

<table-component content='<variable name>' id="mine"></table-component>

This can be done, but you are now stepping into the realm of a framework.

The problem is what is the scope of that variable? Is it a global? Is it a member variable? Is it something else?

If you only want to use Global variables then this is pretty easy. But very limited!:

I DO NOT RECCOMMEND THIS METHOD. It is sloppy and prone to problems.

var obj = { name: "John", age: 30, city: "New York" };

class TabelComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'}).innerHTML = '<h1>your DOM here</h1><pre id="content"></pre>';
  }

  static get observedAttributes() {
    return ['content'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (newVal === null) {
      // Attribute was removed
      this._content = null;
      this.render();
    }
    else {
      this._content = window[newVal];
      this.render();
    }
  }  

  connectedCallback() {
  }

  render() {
    let el = this.shadowRoot.querySelector('#content');
    el.textContent = JSON.stringify(this._content,0,2);
  }

  get content() {
    return this._content;
  }
}

window.customElements.define('table-component', TabelComponent);
<table-component content="obj"></table-component>

This will not auto-update when the value for obj changes. To get the Component to re-read the variable you need to change the value of the attribute. But this is more complicated then just setting the property.

Multiple Properties

No code here, but the simplest version might be is creating multiple properties and each one affects a portion of your DOM. Then you can set each property when it needs to change instead of resetting everything all the time.

like image 100
Intervalia Avatar answered Oct 15 '22 13:10

Intervalia