【JavaScript】もっと見るボタンの実装

## はじめに
[参考記事](#参考記事)を読みながらjqueryで作成された「もっと見るボタン」を実装したのですが、理解のために自分でJavaScriptのコードに置き換えてみました。

以下のコードを読んで、「nth-child以外でわかりやすく書けないか？」と考えたことがきっかけでした。
```
$('.comment-list:nth-child(n + ' + (moreNum + 1) + ')').addClass('is-hidden');
```
（comment-listの6番目以降の要素に対してclass属性```is-hidden```を追加するっていうのはわかったけど...）

### 仕様
- イベントアプリの詳細ページ内でユーザーから送信されたコメントを表示する
- コメントが6件以上の場合に「もっと見るボタン」を表示する
- 「もっと見るボタン」がクリックされるか、コメントが5件以内の場合にボタンを非表示にする。
- コメントはページ内に直近で投稿された10件まで表示する。

## View
今回説明の関係上、Railsのビューに関する説明は省略させて頂きます🙏
```show.html.erb
        <div class="comment-area" class="mt-1 pt-1">
          <% if @comments.present? %>
            <% @comments.each do |comment| %>
              <div class="comment-list">
                <div class="comment-user-info mt-3">
                  <div class="user-thumbnail-container">
                    <% if comment.user %>
                      <% if comment.user.image.attached? %>
                        <%= image_tag comment.user.image, class:"user-thumbnail" %>
                      <% else %>
                        <%= image_tag 'default_avatar.png', class:"user-thumbnail" %>
                      <% end %>
                    <% else %>
                      <%= image_tag 'default_avatar.png', class:"user-thumbnail" %>
                    <% end %>
                  </div>
                  <div class="comment-username">
                    <% if comment.user %>
                      <p class="username"><%= comment.user.name.truncate(13) %>さん</p>
                    <% else %>
                      <p class="username">ゲストさん</p>
                    <% end %>
                  </div>
                </div>
                <p class="created-comment"><span><i class="fa fa-comment-o" aria-hidden="true"></i>&nbsp;&nbsp;<%= comment.content %></span></p>
              </div>
            <% end %>
          <% else %>
            <p id="no-comment">コメントはまだありません</p>
          <% end %>
          <div class="more-list-btn-container">
            <button type="button" class="more-list-btn pointer-cursor">もっと見る</button>
          </div>
        </div>
```

## CSS
```more-list-btn.css
/* もっとみるボタン */
.more-list-btn-container {
  position: relative;
  margin-bottom: 10px;
  z-index: 10;
  border-top: 1px solid var(--light-gray);
  text-align: center;
}

.comment-list.is-hidden {
  opacity: 0;
  height: 0;
  margin: 0;
}

.more-list-btn {
  margin-top: 20px;
  cursor: pointer;
}

.more-list-btn:hover {
  opacity: 0.8;
}

.more-list-btn-container.is-btn-hidden,
.more-list-btn.is-btn-hidden{
  display:none;
  border: none;
}

.more-list-btn {
  display: inline-block;
  background-color: #333;
  color:#fff;
  border: 1px solid #333;
  border-radius: 3px;
  cursor: pointer;
  outline: none;
  padding: 0;
  height: 32px;
  line-height: 32px;
  width: 120px;
}
```

## リプレース前（jquery）のコード
こちらは[参考記事](#参考記事)から自分のコードに置き換えて書きました。
```more_list_btn.js
    // もっとみるボタン
    
    // 表示するリストの数を指定。
    const moreNum = 5;

    $('.more-list-btn-container').removeClass('is-btn-hidden');
    
    // 表示するリストの数以降のリストを隠す。
    $('.comment-list:nth-child(n + ' + (moreNum + 1) + ')').addClass('is-hidden');

    // 全てのリストを表示したら「もっとみる」ボタンをフェードアウトします。
    $('.more-list-btn').on('click', function() {
      $('.comment-list.is-hidden').slice(0, moreNum).removeClass('is-hidden');
      $('.more-list-btn-container').addClass('is-btn-hidden');
      if ($('.comment-list.is-hidden').length == 0) {
        $('.more-list-btn').fadeOut();
      }
    });

    // 全てのリストの数が、表示する数以下だった場合に「もっとみる」ボタンを非表示
    $(function() {
      var list = $(".comment-area .comment-list").length;
        if (list < moreNum) {
          $('.more-list-btn').addClass('is-btn-hidden');
          $('.more-list-btn-container').addClass('is-btn-hidden');
      }
    });
```

## リプレース後（JavaScript）のコード
```more_list_btn.js
  // もっとみるボタン
  
  // 表示するリストの数を指定。
  const moreNum = 5;

　　　// もっと見るボタンを隠す
  const moreListBtn = document.querySelector('.more-list-btn-container');
  if(!moreListBtn) { return false; }
  moreListBtn.classList.remove('is-btn-hidden');


  const commentList = Array.from(document.querySelectorAll('.comment-list'));

  // 表示する数（6件目）以降のリストを隠す
    if(commentList.length > moreNum) {
      commentList.slice(moreNum).forEach(list => {
      list.classList.add('is-hidden');
    })
  }

  // 表示する数（6件目）以降のリストを隠す（filterを使う場合）
  // if(commentList.length > moreNum) {
      // const hiddenList = commentList.filter((list, index) => index > (moreNum-1));
      // hiddenList.forEach(list => {
        // list.classList.add('is-hidden');
      // });
  // }

  // 「もっとみる」ボタンをクリックしたら、全てのリストを表示
  moreListBtn.addEventListener('click', () => {
    const hiddenList = document.querySelectorAll('.comment-list.is-hidden');
    hiddenList.forEach(list => {
      list.classList.remove('is-hidden');
    });

    moreListBtn.classList.add('is-btn-hidden');
    if(hiddenList.length == 0) {
      moreListBtn.style.display = 'none';
    }
  });

  // 全てのリストの数が、表示する数以下だった場合に「もっとみる」ボタンを非表示
  if(commentList.length <= moreNum) {
    moreListBtn.classList.add('is-btn-hidden');
  };
```

## リプレース後のコードの解説（抜粋）
classListなどの基本的なプロパティは、[以前投稿した記事](https://qiita.com/k12da/items/776ac94521c469ce98e6#%E8%A7%A3%E8%AA%AC)で解説しています。
### [Array.from](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/from)
与えられた範囲や反復可能なオブジェクトを配列に変換するためのメソッドです。
class属性```comment-list```を持つ要素を配列として扱うために使用しました。

### [slice](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)
インデックス番号で指定した開始〜終了範囲の文字列もしくは配列要素を切り抜くメソッドです。

例えば上記の例の場合、```slice(moreNum)```のカッコは開始位置を表しています。
```moreNum　= ５```なので、**配列のインデックス番号は5番目**（インデックス番号は０から始まる）。

例えば、以下のような配列データだった場合に6番目以降の要素から取得することになります。
```
const commentList = ['comment1', 'comment2', 'comment3', 'comment4', 'comment5', 'comment6', 'comment7'];
```

### [forEach](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
配列の各要素に対して一回ずつ実行するメソッドです。

他の反復処理を行うメソッドでmapなどがありますが、そちらとは違って配列をループするのみになります。
対してmapは各要素を参照した後に新しい配列を作ります。
(これらのメソッドとの違いに関しては、[こちらの記事](https://qiita.com/diescake/items/70d9b0cbd4e3d5cc6fce)を参照されるとわかりやすいです）

上記のsliceと組み合わせることにより、6件目以降の各要素（list）に対して要素を非表示にするためのclass属性```is-hidden```を追加しています。
```javascript
 commentList.slice(moreNum).forEach(list => {
      list.classList.add('is-hidden');
 })
```

### [filter](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
今回は使いませんでしたが、slice以外の手段としてfilterも使えました。
配列から指定された条件に一致する要素を新しい配列で返します。
ここではcommentListの配列のインデックスが```moreNum = 5``` より大きい要素をフィルタリングしています。
※インデックス番号は0から始まるので、moreNumも１を引いた値で比較しています。
```javascript
commentList.filter((list, index) => index > (moreNum-1));
```

## 結果
![more_list_btn.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/da291782-a298-1289-a346-776cc3a08a0b.gif)

## まとめ
（自分にも言えますが）初学者でコピペしがちな方は、自分なりのコードで書いてみることでロジックを考える習慣に繋がると思いました。他人のコードを読んで理解しようと自然と意識するようにもなるかもです。

## 参考記事
[シンプルに実装できる「もっと見る」ボタン](https://web.runland.co.jp/frontend/post-4137)