chrome拡張機能で抽選機を作った

突然ですが私は極度の優柔不断です。
今日どのゲームするかを決めるのにも迷ってしまいます。
ということで前々から触ってみたかったchrome拡張機能で抽選王的なものを作りました。
~~と言っても今回は拡張機能APIを使う部分が無くてほぼ生のhtmlとjsだけ~~

# 出来たもの
https://github.com/engabesi/RandomPicker
![tyusenk.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469399/17394412-e3ac-f4e0-98ab-6ee93e67ddd3.gif)

# やること
- chrome拡張機能セットアップ
- popup作成

# セットアップ

```:構成予定
RandomPicker/
├─ css/
│   └─ bulma.min.css
├─ html/
│   └─ popup.html
├─ icons/
│   ├─ icon16.png
│   ├─ icon48.png
│   └─ icon128.png
├─ js/
│   └─ popup.js
└─ manifest.json
```

セットアップと仰々しく書きましたが実際はjsonを一つ作るだけです。
chrome拡張機能のsettingsとなる`manifest.json`を作ります。

```json:manifest.json
{
  "manifest_version": 2,
  "version": "1.0",
  "name": "Random Picker",
  "description": "Pick a character string at random",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "browser_action": {
    "default_popup": "html/popup.html"
  }
}

```
`manifest_version`, `version`, `name`は必須です。
また、今回は拡張機能のアイコンをクリックした際に出てくるポップアップ内で抽選ページを作るので`browser_action`の`default_popup`にhtmlファイルを指定します。

詳細は公式ドキュメントや以下の方の記事が参考になります。
https://developer.chrome.com/extensions/manifest
https://qiita.com/mdstoy/items/9866544e37987337dc79

次にhtmlディレクトリを作成し、その中に`popup.html`を作ります。

```html:html\popup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  </head>
  <body>
    <h1>Hello World!</h1>
    <script src="../js/popup.js"></script>
  </body>
</html>
```
ここまでだけでもうポップアップだけですが拡張機能ができました。
次にchromeに導入します。
`chrome://extensions/`をURL欄に入力して拡張機能設定画面に飛びます。
右上のスイッチを切り替えてデベロッパーモードをONにします。
「パッケージ化されていない拡張機能を読み込む」をクリックして`RandomPicker`ディレクトリを指定します。
するとアドレスバーの右側にRのアイコンが出現します。(iconを設定済みであればそのicon)
それをクリックして以下のようにポップアップが出現したら無事拡張機能インストール完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469399/b4417a2c-f2a3-dc3b-400d-ed74e72b4080.png)

# 作る

ここからhtmlとjsを書いていきます。
拡張機能要素はほぼ無いです。
解説するようなものが無いのでコードを貼るだけにしておきます。

## CSSフレームワーク導入
htmlを書く前に、cssを極力書きたくないのでフレームワークを導入します。
今回はbulmaを使用します。
npmやCDN等がありますが、オフライン且つなるべく容量を少なくしたかったのでminだけ引っ張ります。
以下からbulmaをDLし、`bulma.min.css`をcssフォルダ内に入れてインポートします。
https://bulma.io/

## html + js

```html:popup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="../css/bulma.min.css" />
    <style>
      body {
        width: 500px;
      }
    </style>
  </head>
  <body>
    <section class="section">
      <div class="container">
        <!-- header -->
        <h1 id="title" class="title">Random Picker</h1>
        <!-- add area -->
        <div class="columns is-mobile is-vcentered">
          <div class="column">
            <form name="addForm" class="field has-addons">
              <div class="control">
                <input
                  name="addInput"
                  class="input"
                  type="text"
                  placeholder="New Text"
                />
              </div>
              <div class="control">
                <button type="submit" class="button is-info" tabindex="-1">
                  ADD
                </button>
              </div>
            </form>
          </div>
        </div>
        <!-- pick area -->
        <div class="columns is-mobile is-vcentered">
          <div class="column is-7">
            <form name="pickForm" class="field has-addons">
              <div class="control">
                <input
                  name="pickInput"
                  class="input"
                  type="number"
                  placeholder="Count of Pick"
                  value="1"
                />
              </div>
              <div class="control">
                <button type="submit" class="button is-success" tabindex="-1">
                  Pick
                </button>
              </div>
            </form>
          </div>
          <div class="column">
            <button id="clearBtn" class="button is-danger" tabindex="-1">
              Clear
            </button>
          </div>
        </div>
        <div class="columns is-mobile is-multiline">
          <!-- pick list -->
          <div class="column">
            <div
              id="pickList"
              class="column field is-grouped is-grouped-multiline"
            ></div>
          </div>
          <!-- picked list -->
          <div class="column">
            <p class="subtitle is-6">抽選結果</p>
            <div id="pickedList"></div>
          </div>
        </div>
      </div>
    </section>
    <script src="../js/popup.js"></script>
  </body>
</html>
```

```javascript:popup.js
const addForm = document.addForm;
const pickForm = document.pickForm;
const pickListItems = [];

/** LocalStorage */
const saveToLocalStorage = str => {
  if (!str) return;
  localStorage.setItem(str, str);
};

const deleteFromLocalStorage = str => {
  localStorage.removeItem(str);
};

/** pick list */
const pickList = document.getElementById("pickList");

const addString = event => {
  event.preventDefault();
  const inputStr = addForm.addInput.value.trim();
  if (inputStr === "") return;
  appendAddHtml(inputStr);
  addForm.reset();
};

const appendAddHtml = str => {
  const html = createAddHtml(str);
  pickList.innerHTML += html;
  pickListItems.push(str);
  saveToLocalStorage(str);
};

const createAddHtml = str => {
  return `
  <div class="control">
    <div class="tags has-addons">
      <a class="tag is-success del">${str}</a>
      <a class="tag is-delete del"></a>
    </div>
  </div>`;
};

const deleteString = event => {
  if (!event.target.classList.contains("del")) return;
  const parent = event.target.parentElement.parentElement;
  parent.remove();
  const str = parent.textContent.trim();
  pickListItems.forEach((item, index) => {
    if (item === str) pickListItems.splice(index, 1);
  });
  deleteFromLocalStorage(str);
};

const clearStr = () => {
  pickListItems.length = 0;
  pickList.innerHTML = "";
  localStorage.clear();
};

/** picked list */
const pickedList = document.getElementById("pickedList");

const createPickedListHtml = strs => {
  let pickedListHtml = `<div class="list">`;
  strs.forEach(str => (pickedListHtml += `<a class="list-item">${str}</a>`));
  pickedListHtml += "</div>";
  return pickedListHtml;
};

const randomPick = event => {
  event.preventDefault();
  let length = pickListItems.length;
  if (length <= 0) return;
  const pickedStrs = [];
  const pickListItemsCpy = pickListItems.concat();
  const pickInputVal = pickForm.pickInput.value;
  const pickCount = pickInputVal > length ? length : pickInputVal;
  for (let i = pickCount; i > 0; i--) {
    const randomIndex = Math.floor(Math.random() * length);
    pickedStrs.push(pickListItemsCpy[randomIndex]);
    pickListItemsCpy.splice(randomIndex, 1);
    length--;
  }
  pickedList.innerHTML = createPickedListHtml(pickedStrs);
};

addForm.addEventListener("submit", e => addString(e));
pickList.addEventListener("click", e => deleteString(e));
pickForm.addEventListener("submit", e => randomPick(e));
document.getElementById("clearBtn").addEventListener("click", _ => clearStr());

// init
(() => {
  addForm.addInput.focus();
  Object.keys(localStorage).forEach(key => {
    const str = localStorage.getItem(key);
    if (!str) return;
    appendAddHtml(str);
  });
})();
```

これで冒頭に貼った画像のものが完成します。

# まとめ
久しぶりにhtmlとjsを生で書きましたが中々辛い。
各種フレームワーク、ライブラリの有難みを感じる開発でした。
また今回拡張機能API等、拡張機能らしいことをほぼしていないので今度はその辺を絡めたものを作りたい。