簡単にできる!Web Componentの作り方・実例全5件!

さてさて、このブログでは日頃からLaravelとVue.jsの話題をメインにして記事を公開していますが、そのかたわらで新しい技術も吸収していこうという取り組みも行っています。

というのも、ウェブ開発だけに限らずIT系のテクノロジーは常に早いスピードで進化していて、数年経ってしまうとそれが完全に古いものになってしまっていることもあったりするからです。

そして、そんな観点から今回テーマに取り上げるのは

Web Component(うぇぶこんぽーねんと)

です。

ウェブコンポーネントというのは、簡単にいうと独自のHTMLタグがつかえるようになる技術のことで、例えば以下のようにすることができます。

<my-tag></my-tag>

しかも、VueReactなどのフレームワークに依存しませんから、一度必要なウェブコンポーネントを作っておくとどの環境でも使いまわしができますし、さらにフレームワークを利用する場合であっても、それらとの連携も比較的簡単にできます。

ということで、今回は日本語に関連する以下5つのウェブコンポーネントのコードをサンプルとして作成方法をご紹介したいと思います。

  • 年齢表記
  • 日付
  • 都道府県
  • 和暦
  • 郵便番号

※ なお、JavaScriptでパラメータが変更されると自動的に再計算されるようにします。

Web Componentをつくる基本

Web Componentという大層な名前がついていますが、結局のところJavaScriptでHTMLElementというクラスを拡張したものにすぎません。

そのため、単に「ハロー!」と表示するだけの単純なWeb Componentをつくるには、以下のようなJavaScriptファイル(hello.js)をつくります。

class ShowHelloElement extends HTMLElement {

    connectedCallback() {

        this.innerText = 'ハロー!';

    }

}

customElements.define('show-hello', ShowHelloElement);

そして、以下のようにhello.jsを読み込んで<show-hello></show-hello>を普通のHTMLタグとして記述するだけです。

<html>
<body>
    <div>
        <show-hello></show-hello>
    </div>
    <script src="hello.js"></script>
</body>
</html>

ちなみにWeb Componentの名前は、ハイフンを含んだものでなければいけません。

つまり、<hello></hello>という使い方はできず、もしcustomElements.define('hello', MyElement)としてしまうと、以下のようなエラーがコンソールに表示されることになります。

Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': "hello" is not a valid custom element name

では、次からは本題の日本語に関連するコンポーネントを1つずつ紹介していきます。

日本語に関連するWeb Components

年齢(才、歳)を表示するWeb Component

誕生日をセットすれば自動的にその人の年齢を計算してくれるコンポーネントです。なお、単位は「才」と「歳」をどちらでも選べるようにします(デフォルトは「才」)

まずはJavaScript。

class JapaneseAgeElement extends HTMLElement {

    static observedAttributes = ['birth-date'];

    attributeChangedCallback(name, oldValue, newValue) {

        if(name === 'birth-date') {

            this.setInnerText(newValue);

        }

    }

    connectedCallback() {

        const year = this.getAttribute('birth-date');
        this.setInnerText(year);

    }

    setInnerText(dateString) {

        let innerText = '';
        const dt = new Date(dateString);

        if(dt instanceof Date && !isNaN(dt)) {

            innerText = this.calcAge(dt);

        }

        this.innerText = innerText + this.getUnit();

    }

    calcAge(date) {

        const diff = Date.now() - date.getTime();
        const ageDate = new Date(diff);
        return Math.abs(ageDate.getUTCFullYear() - 1970);

    }

    getUnit() {

        let unit = this.getAttribute('unit');

        if(!unit) {

            unit = '才';

        }

        return unit;

    }

}

customElements.define('japanese-age', JapaneseAgeElement);

使い方はこのようになります。

<html>
<body>
    <div>
        <!-- 年齢 -->
        <japanese-age birth-date="2000-01-01" id="age"></japanese-age>
        <japanese-age birth-date="2010-05-031" unit="歳"></japanese-age>
    </div>
    <script src="japanese-age.js"></script>
    <script>

        window.onload = () => {

            document.getElementById('age').setAttribute('birth-date', '1990-03-31');

        };

    </script>
</body>
</html>

タグのプロパティの意味は以下のとおりです。

  • birth-date ・・・ 誕生日。new Date()で日付オブジェクトが作れるならどの表記でも問題ありません。
  • unit ・・・ 「才」と「歳」を指定します。もし何も指定されない場合は「才」と表示されます。

日付を表示するWeb Component

JavaScriptだけでなくPHPなどでもそうですが、開発をしていて地味にめんどうなのが、日本語の曜日を表示したい場合です。もちろん英語表記なら問題ありませんが、月〜日を表示しないといけない場合は独自に用意する必要があります。

このコンポーネントはそういった曜日の表記に対応するものです。

class JapaneseDateElement extends HTMLElement {

    static observedAttributes = ['date'];

    attributeChangedCallback(name, oldValue, newValue) {

        if(name === 'date') {

            this.setInnerText(newValue);

        }

    }

    connectedCallback() {

        const year = this.getAttribute('date');
        this.setInnerText(year);

    }

    setInnerText(dateString) {

        let innerText = '';
        const dt = new Date(dateString);

        if(dt instanceof Date && !isNaN(dt)) {

            const format = this.getFormat();
            let date = (format) ? format : 'Y年m月d日(w)';
            date = date.replace('Y', dt.getFullYear());
            date = date.replace('y', dt.getFullYear().toString().substr(-2));
            date = date.replace('m', dt.getMonth() + 1);
            date = date.replace('d', dt.getDate());
            date = date.replace('w', this.getJapaneseDayOfWeek(dt.getDay()));
            innerText = date;

        }

        this.innerText = innerText;

    }

    getFormat() {

        return this.getAttribute('format');

    }

    getJapaneseDayOfWeek(dayNumber) {

        const days = ['日', '月', '火', '水', '木', '金', '土'];
        return days[dayNumber];

    }

}

customElements.define('japanese-date', JapaneseDateElement);

使い方はこうなります。

<html>
<body>
    <div>
        <!-- 日付 -->
        <japanese-date date="2019-01-31" format="Y年m月d日(w)" id="date"></japanese-date>
        <japanese-date date="2019-12-31" format="y.m.d(w曜日)"></japanese-date>
    </div>
    <script src="japanese-date.js"></script>
    <script>

        window.onload = () => {

            document.getElementById('date').setAttribute('date', '2019-05-31');

        };

    </script>
</body>
</html>

プロパティの意味は次のとおりです。

  • date ・・・ 日付。new Date()が読み取れるものなら何でもOK
  • format ・・・ 日付のフォーマット。wが曜日になります。

そして実際に実行した例はこうなります。

2019年5月31日(金)
19.12.31(火曜日)

都道府県を表示するWeb Component

例えば、JIS規格(JIS X0401)で13は東京の都道府県IDですが、このようにID→都道府県名に変換するコンポーネントになります。

class JapanesePrefElement extends HTMLElement {

    static observedAttributes = ['pref-id'];

    PREFECTURES = {
        '1': '北海道',
        '2': '青森県',
        '3': '岩手県',
        '4': '宮城県',
        '5': '秋田県',
        '6': '山形県',
        '7': '福島県',
        '8': '茨城県',
        '9': '栃木県',
        '10': '群馬県',
        '11': '埼玉県',
        '12': '千葉県',
        '13': '東京都',
        '14': '神奈川県',
        '15': '新潟県',
        '16': '富山県',
        '17': '石川県',
        '18': '福井県',
        '19': '山梨県',
        '20': '長野県',
        '21': '岐阜県',
        '22': '静岡県',
        '23': '愛知県',
        '24': '三重県',
        '25': '滋賀県',
        '26': '京都府',
        '27': '大阪府',
        '28': '兵庫県',
        '29': '奈良県',
        '30': '和歌山県',
        '31': '鳥取県',
        '32': '島根県',
        '33': '岡山県',
        '34': '広島県',
        '35': '山口県',
        '36': '徳島県',
        '37': '香川県',
        '38': '愛媛県',
        '39': '高知県',
        '40': '福岡県',
        '41': '佐賀県',
        '42': '長崎県',
        '43': '熊本県',
        '44': '大分県',
        '45': '宮崎県',
        '46': '鹿児島県',
        '47': '沖縄県'
    };

    attributeChangedCallback(name, oldValue, newValue) {

        if(name === 'pref-id') {

            this.setInnerText(newValue);

        }

    }

    connectedCallback() {

        const year = this.getAttribute('pref-id');
        this.setInnerText(year);

    }

    setInnerText(prefId) {

        this.innerText = (this.PREFECTURES[prefId] !== undefined) ? this.PREFECTURES[prefId] : '';

    }

}

customElements.define('japanese-pref', JapanesePrefElement);

使い方はこうなります。

<html>
<body>
    <div>
        <!-- 都道府県 -->
        <japanese-pref pref-id="1" id="pref"></japanese-pref>
        <japanese-pref pref-id="10"></japanese-pref>
    </div>
    <script src="japanese-pref.js"></script>
    <script>

        window.onload = () => {

            document.getElementById('pref').setAttribute('pref-id', 13);
            
        };

    </script>
</body>
</html>

プロパティの意味は次のとおりです。

pref-id ・・・ JIS規格の都道府県ID

西暦を和暦に変換するWeb Component

最近は少し減った印象がありますが、まだまだ元号を使った年表記の需要は多くあると思います。

このコンポーネントでは、そんな西暦→和暦を自動計算します。

class JapaneseYearElement extends HTMLElement {

    BASE_YEARS = [
        {year: 2018, name: '令和'},
        {year: 1988, name: '平成'},
        {year: 1925, name: '昭和'},
        {year: 1911, name: '大正'},
        {year: 1867, name: '明治'}
    ];
    static observedAttributes = ['year'];

    attributeChangedCallback(name, oldValue, newValue) {

        if(name === 'year') {

            this.setInnerText(newValue);

        }

    }

    connectedCallback() {

        const year = this.getAttribute('year');
        this.setInnerText(year);

    }

    setInnerText(year) {

        this.innerText = (!isNaN(year)) ? this.calcJpYear(year) : '';

    }

    calcJpYear(year) {

        for(let i in this.BASE_YEARS) {

            let era = this.BASE_YEARS[i];
            let baseYear = era.year;
            let eraName = era.name;

            if(year > baseYear) {

                let eraYear = year - baseYear;

                if(eraYear === 1) {

                    return eraName +'元年';

                }

                return eraName + eraYear +'年';

            }

        }

        return '';

    }

}

customElements.define('japanese-year', JapaneseYearElement);

使い方はこうなります。

<html>
<body>
    <div>
        <!-- 和暦 -->
        <japanese-year year="2019" id="year"></japanese-year>
        <japanese-year year="1989"></japanese-year>
    </div>
    <script src="japanese-year.js"></script>
    <script>

        window.onload = () => {

            document.getElementById('year').setAttribute('year', 2020);
            
        };

    </script>
</body>
</html>

郵便番号をハイフンありに統一し、郵便マークをつけるWeb Component

例えば、郵便番号(ハイフンあり/なしはどちらでもOK)を入力するだけで「〒123-4567」というような表示をするコンポーネントになります。

class JapaneseZipElement extends HTMLElement {

    static observedAttributes = ['number'];

    attributeChangedCallback(name, oldValue, newValue) {

        if(name === 'number') {

            this.setInnerText(newValue);

        }

    }

    connectedCallback() {

        const year = this.getAttribute('number');
        this.setInnerText(year);

    }

    setInnerText(zipString) {

        let innerText = '';

        if(zipString.length === 7 && !isNaN(zipString)) {

            innerText = '〒'+ zipString.substring(0, 3) +'-'+ zipString.substring(3);

        } else if(zipString.match(/[0-9]{3}\-[0-9]{4}/)) {

            innerText = '〒'+ zipString;

        }

        this.innerText = innerText;

    }

}

customElements.define('japanese-zip', JapaneseZipElement);

使い方です。

<html>
<body>
    <div>
        <!-- 郵便番号 -->
        <japanese-zip number="1234567" id="zip"></japanese-zip>
        <japanese-zip number="123-4567"></japanese-zip>
    </div>
    <script src="japanese-zip.js"></script>
    <script>

        window.onload = () => {

            document.getElementById('zip').setAttribute('number', '7654321');
            
        };

    </script>
</body>
</html>

おわりに

ということで、今回はWeb Componentの話題を取り上げてみました。

使ってみたところやはり「汎用的に使いやすい!」と感じました。

というのも(繰り返しになりますが)、Web ComponentJavaScriptフレームワークに依存しないのでどの環境でも使えますし、npmにパッケージ化しておけばいつでも簡単に導入もできます。

そして、webcomponent.orgというサイトにはたくさんのWeb Componentがすでに登録されていてそれらを検索できるようになっています(サンプル実行もできますよ!)ので、Web Componentが盛り上がってくれば、「このサイトにいけばほぼ何でも揃ってる」なんてことになるかもしれません。

とても楽しみですね😊✨

ではでは〜!