【チェックボックス】trueとfalseのあいだ

![checkbox_indeterminate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2518562/b740da05-1fbe-ea24-9c24-38055560077d.png)
チェックボックスの状態でON/OFF以外に第３の状態があるのをご存知ですか？
上記の画像の「トップス」の部分の`indeterminate`という状態がそれにあたります。

ARCHETYPではShopify ExpertとしてECサイトのテーマ開発やカスタマイズをやっているのですが、検索や絞り込みのUIに親子関係があるチェックボックスをよく実装します。
今回はそんな親子階層を利用したユーザーがわかりやすいチェックボックスUIを`indeterminate` を用いて実装する方法をご紹介します。

jQueryを利用した実装を紹介されていた[WebSpaceBlog](https://webspace.jp/)様の[記事](https://webspace.jp/blog/30/)を参考にさせていただきました。

# 結果
最初に結果のcodepenを貼り付けておきます。
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="VwyLjNa" data-user="kamipoo" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kamipoo/pen/VwyLjNa">
  中途半端なcheckbox（vanilla）</a> by kamipoo (<a href="https://codepen.io/kamipoo">@kamipoo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 解説
## HTML
HTMLはごくごくシンプルにリストの入れ子にチェックボックスを入れています。
グループを括る親のinputのclassに`parent`を指定します。
子アイテムを括るulのclassに`child`を指定します。
ちなみに`indeterminate`はタグ属性では設定できず、JavaScriptを用いて設定する必要があります。
```html
<ul>
    <li><label><input type="checkbox" class="parent"> カテゴリ1</label>
      <ul class="child">
        <li><label><input type="checkbox"> アイテム１</label></li>
        <li><label><input type="checkbox" checked> アイテム２</label></li>
        <li><label><input type="checkbox"> アイテム３</label></li>
    </ul></li>
  
    <li><label><input type="checkbox" class="parent"> カテゴリ2</label>
      <ul class="child">
        <li><label><input type="checkbox"> アイテム１</label></li>
        <li><label><input type="checkbox" checked> アイテム２</label></li>
        <li><label><input type="checkbox"> アイテム３</label></li>
    </ul></li>
</ul>
```

## JavaScript
jQueryのコードを元にVanilla化させていただきました。

### 処理1
子チェックボックスをクリックした際の動作を定義します。
子チェックボックスの属するグループのチェックされた数を比較して、親チェックボックスの状態を制御しています。

**条件**
1. 全てがチェックされていれば`checked = true` `indeterminate = false`
2. なにもチェックされていなければ`checked = false` `indeterminate = false`
3. 上記以外は `checked = false` `indeterminate = true`

### 処理2
親チェックボックスをクリックした際の動作を定義します。
親チェックボックスのチェック状態を子チェックボックスに適用します。
```javascript
document.addEventListener("DOMContentLoaded", () => {
  //子要素をクリックした時に親要素のチェック状態をコントロール
  document.querySelectorAll('.child').forEach((child) => {
    child.addEventListener('click', (e) => {
      const parent = child.closest('li').querySelector('input[type=checkbox]');
      const checkboxCount = child.querySelectorAll('input[type=checkbox]').length;
      const selectedCount = child.querySelectorAll('input[type=checkbox]:checked').length;
      if (checkboxCount === selectedCount) {
        parent.indeterminate = false;
        parent.checked = true;
      } else if(0 === selectedCount) {
        parent.indeterminate = false;
        parent.checked = false;
      } else {
        parent.indeterminate = true;
        parent.checked = false;
      }
    });
    let e = new Event('click');
    child.dispatchEvent( e );
  });
  
  //親要素をクリックした時に子要素のチェック状態をコントロール
  document.querySelectorAll('.parent').forEach((parent) => {
    parent.addEventListener('click', (e) => {
      parent.closest('li').querySelectorAll('.child input[type=checkbox]').forEach((items) => {
        items.checked = parent.checked;
      });
    });
  });
});
```

Shopifyのテーマ開発はjQueryや各種フレームワークを使用しているアプリが多くコンフリクトを起こすことがしばしばあり、ARCHETYPのエンジニアチームでは脱jQuery、Vanilla推奨で開発しています。

ARCHETYPでは、Shopifyのテーマ開発やアプリ開発をやりたいエンジニアを募集しております！
気になった方はお気軽にエントリーをお待ちしております！
https://www.archetyp.jp/recruit/