0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Custom Elementsの属性を変化させた時に、その描画が変化しない

Last updated at Posted at 2020-07-03

はじめに

 Web Componentsで作成するCustom Elementsを動的に配置した時に、それぞれのElementの値が更新されないという問題に直面しました。解決するのが少しだけ大変でしたので、解決策を共有したいと思います。英語が読める人は「参考」の先のリンクを参照してもらえると、この内容について詳しく知ることが出来ると思います(日本語のページもたくさんあります)。

やりたいこと

 自分で作成したCustom Elementsを、ウェブページに動的に配置する際に、Elementの属性(attribute)に値を入れると、Elementの描画の挙動が変わって欲しい。ざっくりいうと以下のコードがその下に貼り付ける画像のように動いて欲しい。

コード(こんな感じで動いて欲しい!)

index.html
<!DOCTYPE html>
<html lang='en'>

<head>
  <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
  <script type="text/javascript" src="./example-template.js" defer></script>
  <script type="text/javascript" src="./index.js" defer></script>
  </script>
  <!-- Custom Elementを引っ張ってくる "defer" を書かないと動かない  -->
  <title>custom elements example</title>
</head>

<body>
  <div id="parent">
    <!-- ここに動的にcustom elementsを入れたい -->
  </div>
  <!-- Custom Elementの利用。nameTitleとplaceHolderは作成した新しい属性(Attribute) -->
  <example-template name_title="Text form" plac="Please type your name"></example-template>

</body>

</html>
index.js
var parentEle = document.getElementById("parent");
var childElement = document.createElement('example-template');
childElement.setAttribute('name_title', 'Dynamic Insert');
childElement.setAttribute('plac', "値が入って欲しいのだ。");
parentEle.appendChild(childElement);

動作イメージ(こんな感じで動いて欲しい!)

スクリーンショット 2020-07-03 18.29.25.png

ぶち当たった問題

 ところが、index.htmlのjavascriptを書いただけでは、このように動かないんですね、実際にどうなっているかというと、以下みたいな感じです。Text formという最初からbodyに入っているElementは正しく想定どおりに描画されていますが、動的に入れたElementは正しく描画されません。具体的には、属性に入れた値がきちんと入っていないですね。

動作結果(ちゃんと動かないよ!)

スクリーンショット 2020-07-03 18.36.04.png

ちなみに、この時の生成されているHTMLはこんな感じです。Chromeの検証で引っ張ってきています。
image.jpg

解決方法

 解決するには、実はcustom elementの方にコードを書かないといけません。何が問題かと言いますと、作成したcustom elemetがattributeの変更を検出して、値を入れるということができていないんですね。そのため、attributeが変化したことを検出して(attributeChangedCallback)、値を入れないといけないんです。実際に書いたコードは以下です。コメントに説明を入れています。

example-template.js


class ExampleTemplate extends HTMLElement {
    constructor() {
        // superは必ず一番最初に呼び出される。
        super();
        // これより下にCustom Elementに関する記述を行う

        //shadow rootの作成。これが今回の一番上のノード(ルート)
        const shadow = this.attachShadow({ mode: 'open' });

        // 今回はひとまとまりをsectionで包む
        const wrapper = document.createElement('section');

        //* Sectionの下に並べるElementsの情報を入れていく *//
        // h2タグ(タイトル)
        const subtitle = document.createElement('h2');
        subtitle.setAttribute('class', 'subTitle');
        const titleSub = this.getAttribute('name_title');    // nameTitleというAttributeの値を取得
        subtitle.textContent = titleSub;                    // nameTitleの値をh2タグで表示

        // form & input (テキストフィールド)
        const formF = document.createElement('form');
        const textField = document.createElement('input');

        textField.setAttribute('type', 'text');             // inputのtype指定
        textField.setAttribute('class', 'textField');       // classをtextFieldに
        const contents = this.getAttribute('plac');  // placというAttributeの値を取得
        textField.setAttribute('placeholder', contents);    // input(type=text)のplaceholderにplacの値を代入
        formF.appendChild(textField);                       // formの下にtextFieldをネストする

        // Shadow domにCSSを適用する
        const style = document.createElement('style');
        console.log(style.isConnected);
        style.textContent = `
        .textField {
            width: 500px;
            height: 100px;
        }
        `;                                                  //今回はtextFieldの幅と高さを指定した。

        // shadow domを一番上にして、上で作ったものをネストしていく
        // shadow |- style
        //        |- wrapper |- subtitle
        //                   |- formF
        shadow.appendChild(style);
        shadow.appendChild(wrapper);
        wrapper.appendChild(subtitle);
        wrapper.appendChild(formF);
    }

    // Attributeが変更された時に行う処理の定義
    static get observedAttributes() { return ["name_title", "plac"] };    //"nameTitle"と"plac"が変わることを監視させる

    // observedAttributesで監視しているattributeが変わったら呼ばれる関数、attrに呼ばれたattribute名、newValには新しく入った値が入る
    attributeChangedCallback(attr, oldVal, newVal) {

        //このconsoleはデバッグ用
        console.log('my-el attribute changed', attr);
        console.log('new value is ', newVal);

        //attrの値によって分ける
        if (attr === 'name_title') {            // attrがname_titleのとき
            // Create title holder
            this.shadowRoot.querySelector('.subTitle').textContent = newVal;    //.subTitleのtextContentにnewVal(入力値)を入れる
        } else if (attr === 'plac') {
            // Create description holder
            this.shadowRoot.querySelector('.textField').setAttribute('placeholder', newVal);    //.textField(input element)にnewVal(入力値)を入れる
        }
    }
}
// 新しいElementの定義
customElements.define('example-template', ExampleTemplate);
 

参考

[1] Using custom elements, MDN Web Doc
[2] Eric Bidelman, カスタム要素 v1: 再利用可能なウェブ コンポーネント, Google Developers
[3] Custom elements, javascript.info
[4] HTML Living Standard

問合せ先

akira.kashihara@hotmail.com

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?