Google Chrome 拡張機能の開発 - 3 - 保存できるようにする

<font color="#0000ff"><b>やりたいこと：データ保存を可能とし、ユーザーが任意にフォーマット形式を追加できるようにしたい</b></font>
<font color="#ff0000"><b>結論：データ保存は localStorage を使うだけ</b></font>
>```js
//保存
localStorage.setItem(key, val);
//読み込み
hoge = localStorage.getItem(key);
```
>> 追記[2016/01/07]：
この方法はあくまでHTML5の仕様を使ったもので、拡張機能で作っているのであればGoogoleのAPIを利用するのが望ましいようです。
第1回目で、概要が説明されていた記事を読み飛ばしていましたが、それなりに理解が深まった段階なので、記事を読み進めていって顔から火がでました。
とりあえず、いまはリンク先の記事をご覧ください。その内自分でも整理します。
[Chrome拡張の開発方法まとめ　その1：概念編 - chrome.storageAPIとOption Page](http://qiita.com/edit-mode/items/26d7a22233ecdf48fed8#chromestorageapi%E3%81%A8option-page)

<font color="#ff0000"><b>そこからつくったもの：[今回の成果（サンプルコード）](#result)</b></font>

# はじめに
今回の記事は試行錯誤の様子などもありますので、コードの確認は一番下か上にある今回の結果のリンクをご利用ください。

# 前回の開発は・・・
Google Chrome 拡張機能開発の手始めとして、現在表示中のページのタイトルとURLをクリップボードに貼り付ける機能の拡張をしています。
最初はボタンを押して `[タイトル](URL)` の形式でコピー。続いて、ボタンを増やして、`[URL:title=タイトル]` の形式でコピーしました。

# そして今
あるぇ～ 形式増えるたびにボタン増やすなんて。いやだぁ。
というわけなので、任意に追加できるようにしたくなりました。
となるとデータ保存の方法を知る必要があります。

手始めに、クリップボードへのコピー用に利用しているテキストエリアの文字列を保存できるようにすることからはじめてみます。
まずは `popup.html` に保存用のボタンを追加します。

```html:追加部分のみ抜粋
<button id="Save">Save</button>
```
データの保存に関しては、HTML5でのlocalStorageを使えばいいみたいですね。いやぁ～便利なものですね。こうした機会にでも何か作らないと知りもしないもので。

さっそく、次のようにしてみます。

```js:追加部分のみ抜粋
  $("#Save").on("click", function(){
    localStorage.setItem("hoge", $("#text").val());
  });
  function hoge(){
    $("#text").val(localStorage.setItem("hoge"));
  }
  hoge();
```

至って単純なはずなのに動きません。まったく原因がわからず、ログ出しなどで確認することにします。

```js:ログ出力で確認
  $("#Save").on("click", function(){
    console.log("ほげ - 01");
    localStorage.setItem("hoge", $("#text").val());
  });
  function hoge(){
    console.log("ほげ - 02");
    $("#text").val(localStorage.setItem("hoge"));
  }
  hoge();
```

こんな感じで、ログ出力でそもそも動いているのかなど確認しようとしたら、ログも出ない。あれれのれぇ？？ あぁもうなんなのさ。
どうやら拡張機能のログは別で確認しないといけないようですね。

>[2016/01/06]追記：右クリックしてコンテキストメニューより[ポップアップを検証]で見れました。
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/99d038f4-23f3-01e6-17a7-3d95042aac31.png)
以降のやり方は、まぁあれです。とりあえず出来るやり方でやっていたということで。

普通にhtmlファイルを開いて動かしてみることにします。
はい。ちゃんと出てました。HTML表示時ですね。
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/9839a1ce-029f-ffbf-22c1-d46dc3350be2.png)

あっ。はい。setItemを呼んでいます。コピペ作業していたから気づかなかったのですね。超ウケるんですけど。
皆さんも気を付けてください。ここまでに、1時間ほど無駄にしちゃいました。
間違いを修正し、名称なども少し改めて、次のようなりました。

```html:popup.html
Get Title&URL<br>
<button id="Qiita">Qiita</button><button id="はてな">はてな</button>
<textarea id="text"></textarea>
<button id="Save">Save</button>
<script src="jquery-3.1.1.min.js"></script>
<script src="./popup.js"></script>
```

```js:popup.js
$(function(){
  function copyText(val){
      $("#text").val(val);
      $("#text").select();
      document.execCommand('copy');
  }
  $("#Qiita").on("click", function(){
    chrome.tabs.getSelected(null, function(tab){
      copyText("[{0}]({1})".replace("{0}", tab.title).replace("{1}", tab.url));
    });
  });
  $("#はてな").on("click", function(){
    chrome.tabs.getSelected(null, function(tab){
      copyText("[{1}:title={0}]".replace("{0}", tab.title).replace("{1}", tab.url));
    });
  });
  $("#Save").on("click", function(){
    localStorage.setItem("text", $("#text").val());
  });
  function showSaveData(){
    $("#text").val(localStorage.getItem("text"));
  }
  showSaveData();
});
```

動作確認です。まず普通に表示した状態です。
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/cb936bc0-204b-5cc1-32ea-ff90d2a9faab.png)

"ほげ"を入力して、Saveをクリックしました。
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/27be3dba-9515-e70c-fa86-d78dd58f4baa.png)

再度表示させてみます。ちゃんと"ほげ"が表示されました。やったね。
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/6f246350-ac54-0a29-73d0-79328b553492.png)

じゃぁちゃんと機能拡張しよう！

# <a name="result">今回の成果（サンプルコード）
<font color="#0000ff"><b>やりたいこと：リストボックスにフォーマット形式を追加・削除できて、選択している形式でコピーする</b></font>

で、次のようになりました。

```html:popup.html
<button id="Copy">Copy to Clipboard</button><br>
<select id="CopyFormat" size=5></select><br>
<textarea id="text"></textarea><br>
<button id="Add">Add</button> <button id="Delete">Delete</button> <button id="Save">Save</button><br>
<button id="Clear">Clear save data</button>
<script src="jquery-3.1.1.min.js"></script>
<script src="./popup.js"></script>
```

```popup.js
const saveDataName = "SaveData";
$(function(){
  //テキストエリアに文字列を貼りつけて、選択状態にしてクリップボードへコピーする
  const copyText = (val)=>{
    const txt = $("#text");
    txt.val(val);
    txt.select();
    document.execCommand("copy");
  }
  //数値変換
  //TODO: 配列要素の扱いで絶対にエラーを起こさないような対処が必要
  const pi = (x)=>{
    return parseInt(x.substr(1, x.length-2), 10);
  }
  //文字列フォーマット "{}"で囲われた範囲を中の数値と対応する配列の文字列で置き換え
  //TODO: エスケープや例外への対処が必要
  const formatString = (formatValue, replaceStringArray)=>{
    formatValue.match(/({(\d)+})/g).forEach((x)=>{
      formatValue = formatValue.replace(x, replaceStringArray[pi(x)]);
    });
    return formatValue;
  }
  //クリップボードへのコピーボタンクリック {0}:URL　{1}:タイトル
  $("#Copy").on("click", ()=>{
    chrome.tabs.getSelected(null, function(tab){
      copyText(formatString($("#CopyFormat > option:selected").text(), [tab.url, tab.title]));
    });
  });
  //リストへの要素の追加
  const addCopyFormat = (str)=>{
    const list = $("#CopyFormat");
    list.append($("<option>").text(str).val(list.children().length+1));
  }
  $("#Add").on("click", ()=>{
    console.log($("#text").val());
    addCopyFormat($("#text").val());
  });
  //リストへの要素の削除
  $("#Delete").on("click", ()=>{
    $("#CopyFormat > option:selected").remove();
  });
  //リストの内容を保存
  $("#Save").on("click", ()=>{
    let saveData = {"CopyFormat":[], "SelectedIndex":"1"};
    console.log(saveData);
    for (const option of $("#CopyFormat").children())
    {
      saveData.CopyFormat.push(option.text);
    }
    saveData.SelectedIndex = $("#CopyFormat").val();
    localStorage.setItem(saveDataName, JSON.stringify(saveData));
    console.log(saveData);
  });
  //設定データをクリアする
  $("#Clear").on("click", ()=>{
    localStorage.clear();
  });
  //保存データの読み込み
  const loadData = ()=>{
    const data = JSON.parse(localStorage.getItem(saveDataName));
    console.log(data);
    if (data == null){
      //設定データがない場合、リストに初期値を指定
      addCopyFormat("[{1}]({0})");
      $("#CopyFormat").val("1");
    } else {
      data.CopyFormat.forEach((x)=>{ addCopyFormat(x); });
      $("#CopyFormat").val(data.SelectedIndex);
    }
  }
  loadData();
});
```
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/6da1b8ff-f117-feb0-fd42-f12565ec1693.png)
## 変更点
1. 追加、削除、保存、セーブデータのクリアのボタンを追加
2. 初期表示時は`[タイトル](URL)` の形式のみがリストに表示される
3. [Add]で、テキストエリアに入力している内容をリストに追加
4. [Delete]で、リストで選択している項目を削除
5. [Save]でリスト内容を保存
6. [Clear save data]で保存データ(localStorage)をクリアする。

jQueryは10年ぶり、Javascriptも2年ぶりくらいなので、コードがあれこれしているかもしれませんし、まだ例外の対処等していませんが、これで任意に形式を追加できるようになりました。

例えば、こんな感じで追加ができます。
![image](https://qiita-image-store.s3.amazonaws.com/0/156225/2b525790-98c3-5f8e-5bd8-73a10bc7b094.png)![image](https://qiita-image-store.s3.amazonaws.com/0/156225/a13683c7-faa8-1828-820d-1a04d3e87069.png)

# 今回参考にした記事
- [Google Chrome拡張の作り方（その4：オプション画面と保存データのバックグラウンド・ポップアップとの連携）: 小粋空間](http://www.koikikukan.com/archives/2014/06/19-005555.php)
- [HTML5のlocalStorageの使い方のまとめ: 小粋空間](http://www.koikikukan.com/archives/2014/06/09-005555.php)
- [JavascriptのChromeでのデバッグ方法個人的まとめ2016 - Qiita](http://qiita.com/snoguchi/items/8f6bb62a3166eca23ac3)


以上

|前回|一覧|次回|
|:--|:--:|--:|
|[早速自作したものに機能追加していく](http://qiita.com/rrryutaro/items/194bc78ad73a29daef5f)|[シリーズ一覧](http://qiita.com/rrryutaro/items/015b67568ddd947ec2b5)|[マウスカーソルにある位置のものをコピー](http://qiita.com/rrryutaro/items/f4f129dacdf22ce43d14)|