JavaScript初心者向けに「とある要素と対象要素に対する作用の実装」を解説

# はじめに

この記事は社内共有で記載した内容の転記です。
社内だけでなく少しでも誰かのためになれば良いなと思ったので掲載いたします。

JavaScriptちょっと実装できるけど、
もっと綺麗に書きたい。。。もっと効率的にしたい。。。と願っている方を対象としています。

また、実装の中でjQueryを使用しています。
なんだかんだまだ使っている案件もあり、初心者の方には優しいと思うので。。。！
※ 2018年だぜ！？って言わないで😂

# とある要素と対象要素に対する作用の実装とは？

「とある要素と対象要素に対する作用の実装」をぱっと言葉だけ聞いても分かりにくいと思いますが、「要素Aをクリック時に要素aを表示する」みたいな実装です。

- タブ
- アコーディオン

など、いろいろなパターンに用いられていますが、今回は「タブ」をサンプルに解説をしていきます。

## 対象要素の判定

さて、「とある要素と対象要素に対する作用」というのは、
冒頭で例にあげたような「とある要素」に対して何らかのイベントが発火したタイミングで、「対象要素」に対して何らかの作用をもたらすということが多いですが、
ここで処理的に必要なのが「とある要素」と「対象要素」の**関連付けをする**ことです。

「要素A」と「要素a」という表現なら人間的に「同じアルファベットで大文字と小文字だ」と曖昧に判断できますが、コンピュータにははっきりと関係性を教えてあげる必要があります。

### 良くない例

この実装としてよくやりがちなのはJavaScriptで「とある要素と対象要素の**関係を定義する**」という実装です。
例としては以下のような実装です。

```html:HTML
<a href="javascript:;" class="tab tab--1">tab1</a>
<a href="javascript:;" class="tab tab--2">tab2</a>
<a href="javascript:;" class="tab tab--3">tab3</a>

<div class="tab-content tab-content--1">tab-content--1</div>
<div class="tab-content tab-content--2">tab-content--2</div>
<div class="tab-content tab-content--3">tab-content--3</div>
```

```javascript:JavaScript
// タブ要素（とある要素）の代入
var $tab1 = $('.tab--1');
var $tab2 = $('.tab--2');
var $tab3 = $('.tab--3');

// コンテンツ要素（対象要素）の代入
var $tabContent1 = $('.tab-content--1');
var $tabContent2 = $('.tab-content--2');
var $tabContent3 = $('.tab-content--3');

// 要素表示切り替え
var showTabContent1 = function () {
  $tabContent1.show();
  $tabContent2.hide();
  $tabContent3.hide();
}
var showTabContent2 = function () {
  $tabContent1.hide();
  $tabContent2.show();
  $tabContent3.hide();
}
var showTabContent3 = function () {
  $tabContent1.hide();
  $tabContent2.hide();
  $tabContent3.show();
}

// リスナー
$tab1.on('click', showTabContent1);
$tab2.on('click', showTabContent2);
$tab3.on('click', showTabContent3);
```

<p data-height="265" data-theme-id="0" data-slug-hash="aYwVQJ" data-default-tab="css,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="とある要素と対象要素の関係を定義する - 良くない例" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/aYwVQJ/">とある要素と対象要素の関係を定義する - 良くない例</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

正常に動作することはするんですが、
同じような実装の関数がたくさんありますね。

```javascript
// 要素表示切り替え
var showTabContent1 = function () {
  $tabContent1.show();
  $tabContent2.hide();
  $tabContent3.hide();
}
var showTabContent2 = function () {
  $tabContent1.hide();
  $tabContent2.show();
  $tabContent3.hide();
}
var showTabContent3 = function () {
  $tabContent1.hide();
  $tabContent2.hide();
  $tabContent3.show();
}
```

関係性を示しているのは、リスナーでこれらの関数を呼び出している実装そのものになります。

このままでも要件によっては問題ないですが、これが
「動的なサーバサイドレンダリングによって100個同じようになりますー。」
とか言われたら、**100個分定義**しなければならないです。🙄🙄

それは現実的ではないので、JavaScriptでは「とある要素と対象要素の**関係を定義する**」のではなく、「とある要素と対象要素の**関係を判定する**」実装に変えて、
「とある要素と対象要素の関係の定義」はHTML側に任せる。という実装をしたいと思います。

### 良い例

「とある要素と対象要素の関係の定義」はHTML側に任せる。という実装にも様々な方法があります。

- 序列による定義（indexで判定）
- クラス名による定義（classNameで判定）
- data属性による定義（data attributeで判定）

今回は僕がよく実装している「data属性で要素の関係性を示す」方法で「とある要素と対象要素の**関係を判定する**」実装を行なってみます。

```html:HTML
<a href="javascript:;" class="tab" data-tab-id="1">tab1</a>
<a href="javascript:;" class="tab" data-tab-id="2">tab2</a>
<a href="javascript:;" class="tab" data-tab-id="3">tab3</a>

<div class="tab-content" data-tab-id="1">tab-content1</div>
<div class="tab-content" data-tab-id="2">tab-content2</div>
<div class="tab-content" data-tab-id="3">tab-content3</div>
```

```javascript:JavaScript
// タブ要素（とある要素）の代入
var $tabs = $('.tab');
// コンテンツ要素（対象要素）の代入
var $tabContents = $('.tab-content');

// 要素関係の判定と対象要素の返し
var filterTabContent = function (id) {
  return $tabContents.filter(function() {
    return $(this).data('tabId') == id;
  })
}

// 表示切り替え
var switchTabContent = function () {
  var tabId = $(this).data('tabId');
  var $targetContent = filterTabContent(tabId);
  // 一旦すべて非表示
  $tabContents.hide();
  // 対象要素の表示
  $targetContent.show();
}

// リスナー
$tabs.on('click', switchTabContent);
```

<p data-height="265" data-theme-id="0" data-slug-hash="YaQEML" data-default-tab="js,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="とある要素と対象要素の関係を判定する - 良い例" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/YaQEML/">とある要素と対象要素の関係を判定する - 良い例</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

重複するような関数が消え実装がスッキリしました。
「とある要素と対象要素の**関係を判定する**」実装は以下の関数で行なっています。

```javascript
// 要素関係の判定と対象要素の返し
var filterTabContent = function (id) {
  return $tabContents.filter(function() {
    return $(this).data('tabId') == id;
  })
}
```

data属性の値を判定して、対応する要素を返す実装を行なっています。

これで100個同じような要素があってもサーバ側で動的に「data-tab-id」 を出力してもらえればJavaScriptの記述は一切変えずに済みますね！

## まとめ

この「とある要素と対象要素に対する作用」は様々なパターンに応用ができます。
今回のサンプルである「タブ」以外にも

- アコーディオン
- リストからのポップアップ（対象データを取得とか？似たような感じ）
- グローバルメニューのドロップダウン（複雑でJSを利用しないとできないようなものとか）
- セレクトの親子関係（よくある大カテ、中カテ、小カテみたいな）

など、いろいろと応用できるところが多いです。🤠