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