FIRST PLAN株式会社のフロントエンドエンジニアtakeです。
ダイエット頑張ります!!
さて、入社後の課題をやったので発表したいと思います。
お時間があったら見ていってください。
こんなやつ
簡単な設計(JavaScript)
api部分
①フォームに入力した単語を取得。
document.querySelector(element).value
↓
②それをgetのパラメーターのqueryにセット、そしてget。
axios.get('https://qiita.com/api/v2/items', params: {query: ①})
↓
③帰ってきたJSONのdataプロパティを変数に代入。
const response = axios.get('https://qiita.com/api/v2/items', params: {query: ①})
const fugafuga = response.data
↓
③その配列に対して、検索結果をリストレンダリング。
fugafuga.foreach(fuga => document.createElement(foo)~
ページネーション部分
①ページ番号部分をクラス内のプロパティで管理、初期値は1。
this.currentPage = 1;
②「進む」ボタンを押すとcurrentPageを+1、「戻る」場合は-1する。
③そしてそのcurrentPageをgetのpage部分にセット、そしてget。
axios.get('https://qiita.com/api/v2/items', params: {page: this.currentPage})
④最後にapi部分で行っている③、④をこちらに行う。
コード
HTML
マークアップはbootstrapを使いcssでは書かずに実装しました。
動きとしては<tbody>にJavaScript側でcreateElementされた
<tr>
  <td>記事のタイトル</td>
  <td>ライク数</td>
  <td>コメント数</td>
</tr>
が挿入されていくイメージです。
<div class="container p-5">
  <div class="row flex-column">
    <h1>
      <span class="text-success">Qiita</span>記事検索
    </h1>
    <form class="mb-3">
      <div class="form-group">
        <label for="search-box">キーワードで検索</label>
        <input
          type="text"
          class="form-control"
          id="search-box"
          placeholder="キーワードを入力"
        />
      </div>
      <button type="button" class="btn btn-success search-button">検索</button>
    </form>
  </div>
  <div class="search-result row d-none">
    <div class="table-responsive">
      <table class="table table-striped">
        <thead>
          <tr>
            <th scope="col">タイトル</th>
            <th scope="col">LGTM</th>
            <th scope="col">コメント数</th>
          </tr>
        </thead>
        <tbody>
          <!-- ここに挿入される-->
        </tbody>
      </table>
    </div>
  </div>
  <div class="search-result row d-none">
    <nav class="my-0 mx-auto" aria-label="page-list">
      <ul class="pagination">
        <li class="page-item return-button">
          <a
            class="page-link"
            aria-label="Previous"
          >
            <span aria-hidden="true">«</span>
            <span class="sr-only">Previous</span>
          </a>
        </li>
        <li class="page-item">
          <a class="page-link page-number" href="#!">1</a>
        </li>
        <li class="page-item next-button">
          <a class="page-link" href="#!" aria-label="Next">
            <span aria-hidden="true">»</span>
            <span class="sr-only">Next</span>
          </a>
        </li>
      </ul>
    </nav>
  </div>
</div>
JavaScript
http通信ではaxiosを使いました。
「簡単な設計」で書いた事を今回はあえてフルスクラッチで実装しています。
renderingResults関数の部分ではReactのmap、Vueのv-forのような事を力技で実装しています。
それと少し表示が寂しいので、アラート部分ではsweetalert2を導入しました。
※まだまだコードを綺麗にできると思うので、リファクタリングして更新します。
const $ = element => document.querySelector(element);
class ApiSearch {
  constructor() {
    //検索結果のJSONを格納
    this.results = '';
    //現在のページ数
    this.current_page = 1;
  }
  renderingResults() {
    //表示前に以前のDOMを削除
    while ($('tbody').firstChild) $('tbody').removeChild($('tbody').firstChild);
    this.results.forEach(result => {
      //追加するテーブルのエレメントを作成して中身を追加
      const TR = document.createElement('tr');
      TR.style.cursor = 'pointer';
      TR.addEventListener('click', () => {
        window.open(result.url, '_blank');
      });
      const TITLE = document.createElement('td');
      TITLE.textContent = result.title;
      const LGTM = document.createElement('td');
      LGTM.textContent = result.likes_count;
      const COMMENT = document.createElement('td');
      COMMENT.textContent = result.comments_count;
      //tbodyに順に子要素を追加
      $('tbody').appendChild(TR);
      TR.appendChild(TITLE);
      TR.appendChild(LGTM);
      TR.appendChild(COMMENT);
    });
  }
  pageTransition(identifier) {
    //検索前は実行しない
    if (!this.results) return;
    //戻るか進むかの処理分岐
    if (identifier === 'next') {
      this.current_page++;
    } else {
      if (this.current_page <= 1) return;
      this.current_page--;
    }
    $('.page-number').textContent = this.current_page;
    this.searchResults();
  }
  async searchResults() {
    //キーワードが空の場合はアラート
    if (!$('#search-box').value) {
      Swal.fire({
        title: 'Caution!',
        text: 'キーワードを入力してください',
        icon: 'warning'
      });
      return;
    }
    //apiを叩く
    try {
      const PARAMS = {
        page: this.current_page,
        per_page: 20,
        query: $('#search-box').value
      };
      const RESPONSE = await axios.get('https://qiita.com/api/v2/items', {
        headers: {
          Authorization:
            'Bearer アクセストークン'
        },
        params: PARAMS
      });
      //JSONを配列に格納
      this.results = RESPONSE.data;
    } catch {
      //エラーの場合はalert
      Swal.fire({
        title: 'Error!',
        text: '通信エラーです',
        icon: 'error'
      });
    }
    //テーブルとページネーションを表示。
    document.querySelectorAll('.search-result').forEach(result => {
      if (result.classList.contains('d-none')) result.classList.remove('d-none');
    })
    this.renderingResults();
  }
}
//クラスをインスタンス化
const APISEARCH = new ApiSearch();
//イベントを紐づけ
$('#search-box').addEventListener('keydown', e => {
  //デフォルトの動作をキャンセルしてsearchResultsを発火する。
  if (e.key === 'Enter') {
    e.preventDefault();
    APISEARCH.searchResults();
  }
});
$('.search-button').addEventListener('click', () => {
  APISEARCH.searchResults();
});
$('.next-button').addEventListener('click', e => {
  e.preventDefault();
  APISEARCH.pageTransition('next');
});
$('.return-button').addEventListener('click', e => {
  e.preventDefault();
  APISEARCH.pageTransition('return');
});
