LoginSignup
3
3

More than 3 years have passed since last update.

BootstrapとJavascriptでQiita検索アプリを作る

Last updated at Posted at 2020-05-22

FIRST PLAN株式会社のフロントエンドエンジニアtakeです。

ダイエット頑張ります!!

さて、入社後の課題をやったので発表したいと思います。
お時間があったら見ていってください。

こんなやつ

Image from Gyazo

簡単な設計(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する。
③そしてそのcurrentPagegetpage部分にセット、そして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>

が挿入されていくイメージです。

HTML
<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">&laquo;</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">&raquo;</span>
            <span class="sr-only">Next</span>
          </a>
        </li>
      </ul>
    </nav>
  </div>
</div>

JavaScript

http通信ではaxiosを使いました。
「簡単な設計」で書いた事を今回はあえてフルスクラッチで実装しています。

renderingResults関数の部分ではReactのmap、Vueのv-forのような事を力技で実装しています。
それと少し表示が寂しいので、アラート部分ではsweetalert2を導入しました。

※まだまだコードを綺麗にできると思うので、リファクタリングして更新します。

JavaScript
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');
});
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3