chrome拡張機能の開発記録２

先日、自身の二作目であるchrome拡張機能『[YouTube Video Tag](https://chrome.google.com/webstore/detail/youtube-video-tag/mdbppohmnfpelimdahfbickpchilbima)』をリリースしました。

今回の記事では、[前回の記事](https://qiita.com/matonrin/items/1df4154a39b748910947)には載っていない新たな学びを書き記していこうと思います。

※以下の本文では、私が作った拡張機能のソースコードを引用している所があります。全てを載せると長くなる部分は省いていますが、詳細を見たい方はChrome extension source viewerなどのツールを使うか、拡張機能をインストールしてローカルストレージのファイルを見るなどして下さい（GitHubのリポジトリは公開してないので）。

---

### オブジェクトのディープコピーを作る
オブジェクトのディープコピーを作るには、文字列に変換（`JSON.stringify`）してから元に戻します（`JSON.parse`）。

```JavaScript
let goods = {
    "name": "pen",
    "price": 150
};
let goods_copy = JSON.parse( JSON.stringify(goods) );

goods["price"] = 200;
console.log(goods_copy["price"]); // > 150
```

---

### コールバック関数内からインスタンスを参照する
メソッド内でイベントリスナーを定義する際、コールバック関数内で`this`を使うと、インスタンスではなく`document`を指すことになり、メンバーが見つかりません（`undefined`になる）。

```JavaScript
class Hoge {
    name = "hoge";

    piyo(){
        $('#fuga-btn').on('click', function(){
            console.log (this.name); // > undefined
        });
    }
}
```

コールバック関数をアロー関数で定義すれば、その中で使われた`this`は関数が定義された場所における`this`――つまりインスタンスを指します。

```JavaScript
class Hoge {
    name = "hoge";

    piyo(){
        $('#fuga-btn').on('click', () => {
            console.log (this.name); // > hoge
        });
    }
}
```

---

### onメソッドの処理は重複する
jQueryの`on`メソッドを使ったイベントリスナーの追加は、**重複します**。
重複させたくない場合は、`on`メソッドの前に`off`メソッドを呼んで、既に追加されているイベントリスナーを削除する必要があります（追加されていなくてもエラーが出ることはありません。）。

例えば、私が作った拡張機能には以下のような関数があります。
「タグ要素をページに追加し、それぞれについている『削除』ボタンにイベントリスナーを追加する」という処理を行います。

```JavaScript
addVideoTagsElem(tags){
    let tagGroupElem = $("#yvt-tag-group");
    for (let tag of tags) {
        let tagElem = /* 略 */;
        tagGroupElem.append(tagElem);
    }
    $('.yvt-btn-delete').off('click');
    $('.yvt-btn-delete').on('click', e => {
        // 略
    });
}
```

この関数は、タグの追加が行われる度に呼び出されます。
`on`メソッドを呼び出す対象要素をクラスで指定しているので、タグの追加が複数回行われると、前に追加されていたタグ要素にもイベントリスナーが追加され、重複してしまいます。

そこで、`on`メソッドを呼ぶ直前に`off`メソッドを呼び、既存のタグ要素に追加されているイベントリスナーを削除しています。

---

## タグ入力用ライブラリについて
私が作った拡張機能では、タグを入力する場所が二箇所あり、それぞれでタグ入力用ライブラリを使い分けています。

[tagify](https://github.com/yairEO/tagify)と[tagsinput](https://github.com/underovsky/jquery-tagsinput-revisited)です。

最初はtagifyだけを使う予定でしたが、tagifyの不具合が原因で片方では使えなくなったので、別のライブラリも使うことにしました。

以下では、それぞれのライブラリを使ってみた感想などを述べていこうと思います。

### tagify

tagifyはUIがキレイで、オプションなども充実していて、拡張性も高いライブラリです。
日本語の入力補完にも対応していて、`delimiter`の値を変更することで、全角スペース区切りにも対応します。

しかし、chrome拡張機能で使うとなったとき、いくつか問題が発生しました。

#### ダークテーマのページにあるテキストフォームにtagifyを適用すると、`input`フォームの背景が黒くなり、入力した文字が見えなくなる。

この問題は、tagifyによって追加された`tags`タグの背景色を明示的に変更することで解決しました。

#### `delimiter`に全角スペースを指定していても、`removeAllTags`メソッドでテキストフォーム内の全角スペースを削除できない。

確かに全角スペースを入力すると区切り文字として認識はされるのですが、半角スペースとは違い、全角スペースはテキストフォーム内にそのまま残ってしまいます。
この問題は、`tagify.DOM.input.textContent = "";`を実行し、テキストフォーム内に残った文字列を削除することで解決しました。

#### ページにショートカットキーが設定されている場合、tagifyを適用したテキストフォーム内でのキー入力が、ショートカットキーの機能を呼び出してしまう。

具体例を挙げると、YouTube動画ページ内にtagifyを適用したテキストフォームがある場合、その中で`k`、`j`、`l`、`m`などのキーを押すと、動画の再生が止まったり、シークバーが動いたり、ミュートになったりしてしまうということです。

`keydown`イベントが発生したら`stopPropagation`を実行するなど、色々試してみましたが、完全な解決には至らなかったので、私は「既存のページに追加したテキストフォームにtagifyを適用する」という使い方を断念しました。

ポップアップページでは問題なく使えました。

### tagsinput

tagifyの代わりに動画ページで使うことにしたのが、このライブラリです。

特にこれにこだわる理由もないのですが、日本語の入力補完ができ、ショートカットキーが暴発することもなかったので、採用しました。

難点は、tagifyと違って全角スペースを区切り文字にできないことと、`input`要素の`id`が変更されるので、入力された値を取得するのが少しだけ面倒なことです。