12.5 検索機能の実装と全体の結合
いよいよ、この章も大詰めです。
最後に、検索キーワードに合わせて結果を絞り込む機能を追加し、HTML が最初に読み込まれた時の処理を変更します。
book-search.js のソースコードを、以下の様に実装してください。これで全ての機能が完成します。
ソースフォルダ:public/ch12
ファイル名:book-search.js
// book-search.js
// すべての処理を統合する、起動時のメイン処理(最上部のコードを以下のように完成させます)
document.addEventListener("DOMContentLoaded", async () => {
// 1. 通信を開始してデータを取得
const data = await fetchBookData();
// 2. クラスをインスタンス化してデータを管理
const manager = new BookManager();
manager.setBooks(data.slice(0, 15)); // 練習用に最初の15件を使用します
// 3. 初期状態のリストを表示
renderList(manager.getProcessedItems());
// 4. 検索機能を有効化
setupSearch(manager);
});
// 書籍データを取得する関数(枠組み)
async function fetchBookData() {
try {
// 1. fetchを使って外部サーバーへデータのリクエストを送信します
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
// 2. 通信が成功したか(ステータスコードが200系か)をチェックします
if (!response.ok) {
throw new Error(`HTTPエラーが発生しました。ステータス: ${response.status}`);
}
// 3. 届いたデータをJavaScriptで扱えるオブジェクト形式(JSON)に変換します
const books = await response.json();
// 通信が成功したことを確認するためのメッセージ
console.log(`通信成功!${books.length}件のデータを取得しました。`);
return books;
} catch (error) {
// 通信に失敗した場合のエラーメッセージ
console.log(`データの取得に失敗しました: ${error.message}`);
return [];
}
}
// 書籍データを効率的に管理するためのクラス
class BookManager {
constructor() {
this.books = []; // データを保持するプロパティ
}
// 取得したデータをクラス内にセットします
setBooks(data) {
this.books = data;
}
// 画面表示に不要な情報を削り、整形されたデータを返します
getProcessedItems() {
// mapを使って、idとtitleだけの新しいオブジェクト配列を作成します
return this.books.map(book => ({
id: book.id,
title: book.title
}));
}
}
// 画面のリスト(ul要素)を更新する関数
function renderList(items) {
const listElement = document.querySelector("#book-list");
listElement.innerHTML = ""; // 一度中身を空にして初期化します
// 1件ずつHTML要素(liタグ)を作成して追加します
items.forEach(item => {
const li = document.createElement("li"); // <li>を作成
li.textContent = `[ID:${item.id}] ${item.title}`; // テキストを設定
listElement.appendChild(li); // ulの中に追加
});
}
function setupSearch(manager) {
const searchBox = document.querySelector("#search-box");
searchBox.addEventListener("input", (event) => {
const keyword = event.target.value;
// キーワードを含むものだけを抽出
const filtered = manager.books.filter(book =>
book.title.includes(keyword)
);
renderList(filtered);
});
}
実行結果

解説
処理全体の流れ
【ステップ 1DOMContentLoaded イベント】
HTML の解析が完了したタイミングで実行されます。第 8 章で学んだ内容ですね。
async をつけている理由は、関数内で await を使うため(fetchBookData()の完了を待つ)です。
3: document.addEventListener("DOMContentLoaded", async () => {
【ステップ 2 データ取得】
await をつけることで、データ取得が完了するまで次の処理(8 行目以降)に進みません。
もし await がないと、データ取得中なのに先に進んでしまい、data が undefined の状態でエラーになってしまいます。
6: const data = await fetchBookData();
【ステップ 3-4】クラスへのデータ格納
slice(0, 15)で最初の 15 件だけを取り出しています。これは練習用のため、データ量を減らしているだけです。実際のアプリでは、manager.setBooks(data); として全件を使用します。
9: const manager = new BookManager();
10: manager.setBooks(data.slice(0, 15));
【ステップ 5-6】画面への表示
getProcessedItems()で加工されたデータを取得し、それを renderList()に渡して画面に表示します。
13: renderList(manager.getProcessedItems());
【ステップ 7】検索機能の有効化
検索ボックスでのイベント監視を開始します。manager オブジェクトを渡すことで、setupSearch 内で元のデータにアクセスできるようになります。
16: setupSearch(manager);
event.target.value の取得
82 行目で入力されたテキストを取得しています。
82: const keyword = event.target.value;
【event オブジェクト】
イベントが発生したときの詳細情報を持つオブジェクトです。
addEventListener の引数として自動的に渡されます。
【event.target】
イベントが発生した要素(今回は検索ボックス)を指します。
【event.target.value】
その要素の現在の値(入力されているテキスト)を取得します。
filter + includes の組み合わせ
84-86 行目で、入力されたキーワードを含む書籍だけを抽出しています。
84: const filtered = manager.books.filter(book =>
85: book.title.includes(keyword)
86: );
【filter メソッド(第 5 章の復習)】
配列の各要素をテストして、条件に合うものだけを新しい配列として返します。
【includes メソッド】
文字列に指定した文字列が含まれているか(部分一致)を判定します。
includes(“test”)
“this is a test” → true(含まれている)
“hello world” → false(含まれていない)
【大文字小文字の区別】
includes()は大文字小文字を区別し、”java” と “Java” は別物として扱われます。
大文字小文字を区別しない場合は、以下のように小文字に統一してから判定すると良いでしょう。
book.title.toLowerCase().includes(keyword.toLowerCase())
リアルタイム絞り込みの仕組み
87 行目で、絞り込み結果を画面に反映しています。
87: renderList(filtered);
【動作の流れ】
- ユーザーが 1 文字入力
- input イベント発火
- filter()で絞り込み
- renderList()で再描画
- 画面が更新される
この 1-5 が、文字を入力するたびに瞬時に実行されます。
