web-dev-qa-db-fra.com

Composants Web, transmettre des données vers et depuis

Ma compréhension est que les données sont transmises à un élément html personnalisé via ses attributs et envoyées en envoyant un CustomEvent.

Les objets JavaScript peuvent évidemment être envoyés dans le champ détail de l'événement, mais que faire si l'élément a besoin de beaucoup de données qui lui sont transmises. Existe-t-il un moyen de lui fournir un objet en JavaScript.

Que se passe-t-il si l'élément contient par exemple un nombre variable de parties qui doivent être initialisées ou modifiées dynamiquement (par exemple, une table avec un nombre variable de lignes)? Je peux imaginer définir et modifier un attribut composé d'une chaîne JSON qui est analysée à l'intérieur du composant, mais cela ne semble pas être une manière élégante de procéder:

<my-element tableRowProperties="[{p1:'v1', p2:'v2'}, {p1:'v1',p2:'v2'}, {p1:'v1',p2:'v2'}]"></my-element>

Ou pouvez-vous faire en sorte que l'élément écoute les événements de l'extérieur qui contiennent une charge utile de données?

16
Johan Lundquist

Transmission de données

Si vous voulez/devez vraiment transmettre de grandes quantités de données dans votre composant, vous pouvez le faire de quatre manières différentes:

1) Utilisez une propriété. C'est la plus simple puisque vous passez simplement l'objet en donnant la valeur à l'élément comme ceci: el.data = myObj;

2) Utilisez un attribut. Personnellement, je déteste cette façon de procéder, mais certains frameworks exigent que les données soient transmises via des attributs. Ceci est similaire à la façon dont vous montrez votre question. <my-el data="[{a:1},{a:2}....]"></my-el>. Veillez à suivre les règles liées à échappement des valeurs d'attribut . Si vous utilisez cette méthode, vous devrez utiliser JSON.parse sur votre attribut et cela peut échouer. Il peut également devenir très laid dans le HTML d'avoir la quantité massive de données affichées dans un attribut.

3 Passez-le à travers les éléments enfants. Pensez au <select> élément avec le <option> éléments enfants. Vous pouvez utiliser n'importe quel type d'élément en tant qu'enfants et ils n'ont même pas besoin d'être de vrais éléments. Dans votre fonction connectedCallback, votre code saisit simplement tous les enfants et convertit les éléments, leurs attributs ou leur contenu en données dont votre composant a besoin.

4 Utilisez Fetch. Fournissez une URL pour que votre élément aille chercher ses propres données. Penser à <img src="imageUrl.png"/>. Si vous disposez déjà des données pour votre composant, cela peut sembler être une mauvaise option. Mais le navigateur offre une fonctionnalité intéressante d'intégration de données similaire à l'option 2 ci-dessus, mais gérée automatiquement par le navigateur.

Voici un exemple d'utilisation de données intégrées dans une image:

img {
  height: 32px;
  width: 32px;
}
<img src="data:image/svg+xml;charset=utf8,%3C?xml version='1.0' encoding='utf-8'?%3E%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 314.7 314.7'%3E%3Cstyle type='text/css'%3E .st0{fill:transparent;stroke:%23231F20;stroke-width:12;} .st1{fill:%23231F20;stroke:%23231F20;stroke-width:10;stroke-linejoin:round;stroke-miterlimit:10;} %3C/style%3E%3Cg%3E%3Ccircle class='st0' cx='157.3' cy='157.3' r='150.4'/%3E%3Cpolygon class='st1' points='108,76.1 248.7,157.3 108,238.6'/%3E%3C/g%3E%3C/svg%3E">

Et voici un exemple d'utilisation de données intégrées dans un composant Web:

function readSrc(el, url) {
    var fetchHeaders = new Headers({
      Accept: 'application/json'
    });

    var fetchOptions = {
      cache: 'default',
      headers: fetchHeaders,
      method: 'GET',
      mode: 'cors'
    };

    return fetch(url, fetchOptions).then(
      (resp) => {
        if (resp.ok) {
          return resp.json();
        }
        else {
          return {
            error: true,
            status: resp.status
          }
        }
      }
    ).catch(
      (err) => {
        console.error(err);
      }
    );
  }

  class MyEl extends HTMLElement {
    static get observedAttributes() {
      return ['src'];
    }

    attributeChangedCallback(attrName, oldVal, newVal) {
      if (oldVal !== newVal) {
        this.innerHtml = '';
        readSrc(this, newVal).then(
          data => {
            this.innerHTML = `<pre>
${JSON.stringify(data,0,2)}
            </pre>`;
          }
        );
      }
    }
  }

  // Define our web component
  customElements.define('my-el', MyEl);
<!--
This component would go load its own data from "data.json"
<my-el src="data.json"></my-el>
<hr/>
The next component uses embedded data but still calls fetch as if it were a URL.
-->
<my-el src="data:json,[{&quot;a&quot;:9},{&quot;a&quot;:8},{&quot;a&quot;:7}]"></my-el>

Vous pouvez faire la même chose en utilisant XHR, mais si votre navigateur prend en charge les composants Web, il prend probablement en charge la récupération. Et il existe plusieurs bons polyfills si vous en avez vraiment besoin.

Le meilleur avantage de l'utilisation de l'option 4 est que vous pouvez obtenir vos données à partir d'une URL et vous can directement incorporer vos données. Et c'est exactement ainsi que la plupart des éléments HTML prédéfinis, comme <img> travail.

[~ # ~] mise à jour [~ # ~]

J'ai pensé à une 5ème façon d'obtenir des données JSON dans un objet. C'est en utilisant un <template> tag dans votre composant. Pour cela, vous devez toujours appeler JSON.parse mais cela peut nettoyer votre code car vous n'avez pas besoin d'échapper autant au JSON.

class MyEl extends HTMLElement {
  connectedCallback() {
    var data;
    
    try {
      data = JSON.parse(this.children[0].content.textContent);
    }
    
    catch(ex) {
      console.error(ex);
    }

    this.innerHTML = '';
    var pre = document.createElement('pre');
    pre.textContent = JSON.stringify(data,0,2);
    this.appendChild(pre);
  }
}

// Define our web component
customElements.define('my-el', MyEl);
<my-el>
  <template>[{"a":1},{"b":"&lt;b>Hi!&lt;/b>"},{"c":"&lt;/template>"}]</template>
</my-el>

Transmission de données

Il existe trois façons d'extraire des données du composant:

1) Lisez la valeur d'une propriété. C'est idéal car une propriété peut être n'importe quoi et devrait normalement être au format des données que vous souhaitez. Une propriété peut renvoyer une chaîne, un objet, un nombre, etc.

2) Lisez un attribut. Cela nécessite que le composant maintienne l'attribut à jour et peut ne pas être optimal car tous les attributs sont des chaînes. Votre utilisateur devra donc savoir s'il doit appeler JSON.parse sur votre valeur ou non.

3) Événements. C'est probablement la chose la plus importante à ajouter à un composant. Les événements doivent se déclencher lorsque l'état change dans le composant. Les événements doivent se déclencher en fonction des interactions de l'utilisateur et simplement pour alerter l'utilisateur que quelque chose s'est produit ou que quelque chose est disponible. Traditionnellement, vous incluiez les données pertinentes dans votre événement. Cela réduit la quantité de code que l'utilisateur de votre composant doit écrire. Oui, ils peuvent toujours lire les propriétés ou les attributs, mais si vos événements incluent toutes les données pertinentes, ils n'auront probablement pas besoin de faire plus.

24
Intervalia

Il y a une 6ème manière qui est vraiment similaire à la réponse de @ Intervalia ci-dessus mais utilise un <script> tag au lieu d'un <template> tag.

Il s'agit de la même approche utilisée par n élément Markdown .

class MyEl extends HTMLElement {
  connectedCallback() {
    var data;
    
    try {
      data = JSON.parse(this.children[0].innerHTML);
    }
    
    catch(ex) {
      console.error(ex);
    }

    this.innerHTML = '';
    var pre = document.createElement('pre');
    pre.textContent = JSON.stringify(data,0,2);
    this.appendChild(pre);
  }
}

// Define our web component
customElements.define('my-el', MyEl);
<my-el>
  <script type="application/json">[{"a":1},{"b":"<b>Hi!</b>"},{"c":"</template>"}]</script>
</my-el>
3
logan