この記事では、入門書などを読んで JavaScript の文法をひととおり知ったあとに、より実践的に JavaScript の活用法を学べるクイズを紹介します。応用が効くように、実際の開発で「ありがち」な実装パターンを扱っていきたいと思います。
問題
もっと見る機能を作ろう
画面が小さい場合は右上の「EDIT ON CODEPEN」をクリックすると CodePen のサイトにジャンプして大きな画面で見られます。
See the Pen JavaScript実践クイズ〜GIFガチャ編〜 by Masahiro Harada (@MasahiroHarada) on CodePen.
後述する仕様の通りに動作するように JavaScript を編集しましょう。
穴埋めになっているので /* Insert code here... */
の箇所にコードを足してください。
HTML と CSS は出来ているので変更しません。
今回使用する API(ruddy-mail.glitch.me)は Glitch で作成しました。コードは以下の通りです。
(前回「GIFガチャ編」で使用した API のコードも含まれています。)
前提条件
- jQuery などのライブラリは使用せずに実装してください。
- 対応ブラウザは Google Chrome 最新版とします。
- 解答例では ES2015 以降の文法も使用します。
- 非同期通信には Axios というライブラリを用います。
(CodePen には事前に読み込んであります。)
仕様
- 最初は「もっと見る」ボタンだけが表示されています。
- 「もっと見る」ボタンをクリックすると、猫のGIFが一行に三枚表示されます。
- 表示できる GIF がなくなったら「もっと見る」ボタンを非表示にします。
GIF 画像取得 API(/api/list
)は以下の形式の JSON を返却します。
返却できる GIF がなくなると、last
が true
になります。
{
"items": [
{ "url": string },
{ "url": string },
{ "url": string }
],
"last": boolean
}
「もっと見る」をクリックしたあと、このような HTML を構築してください。
<div id="output" class="gifs">
<div class="columns">
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
</div>
</div>
「もっと見る」がさらにクリックされたときは、このように要素を追加します。
<div id="output" class="gifs">
<div class="columns">
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
</div>
<div class="columns">
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
</div>
</div>
回答例
const moreButton = document.getElementById('more');
const output = document.getElementById('output');
const API_URL = 'https://ruddy-mail.glitch.me/api/list';
let page = 1;
moreButton.addEventListener('click', function() {
axios.get(API_URL + '?page=' + page)
.then(response => {
// HTMLを追加する
const row = buildHTML(response.data.items);
output.appendChild(row);
// 最後のページであれば「もっと見る」ボタンは隠す
if (response.data.last) {
moreButton.classList.add('hidden');
}
});
page += 1;
});
/**
* 一行分の要素を生成する
*
* @param {Array} items
*/
function buildHTML(items) {
const row = document.createElement('div');
row.className = 'columns';
let html = '';
items.forEach(item => {
html += '<img class="column is-one-third" src="' + item.url + '" alt="" />';
});
row.innerHTML = html;
return row;
}
解説
前回のGIFガチャ編に引き続き Promise を利用した非同期処理がポイントの一つですが、それに加えて今回は「HTML を追加する」パターンが登場しています。
今まではクラスを利用して要素を出したり消したりしていただけでしたが、今回は HTML そのものを JavaScript で作って既存の要素の中に追加する実装を行います。
まず API からはレスポンスとして以下のようなデータを受け取ります。
{
"items": [
{ "url": "https://media.giphy.com/media/vFKqnCdLPNOKc/giphy.gif" },
{ "url": "https://media.giphy.com/media/33OrjzUFwkwEg/giphy.gif" },
{ "url": "https://media.giphy.com/media/l0MYNB04rBb51QNtC/giphy.gif" }
],
"last": false
}
このうち HTML 作成に使うのは items
配列です。
last
は後ほど「もっと見る」ボタンの表示判定に使用します。
そして以下の HTML を作成します。
<div class="columns">
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
<img class="column is-one-third" src="..." alt="" />
</div>
この処理を受け持つのが buildHTML
関数です。つまり buildHTML
関数は、レスポンスの items
配列を引数にとり、上記の HTML 要素を返却します。
buildHTML
関数ではまず外側の <div>
要素を createElement
メソッドで作成しています。
const row = document.createElement('div');
createElement
メソッドの返却値は getElementById
などと同じ HTML 要素です。
そのため、className
プロパティからクラスを指定できます。
row.className = 'columns';
さて createElement
は HTML 要素を作成するメソッドでしたが、ある要素の中に別の要素を追加する方法はいくつかあり、今回は以下の二つを使用しています。
elem.innerHTML = "<img src="..." alt="..." />";
elem.appendChild(childElem);
innerHTML
プロパティを用いると、文字列で表される HTML 要素が子要素に追加されます。子要素全体が上書きされ置き換わるのがポイントです。
appendChild
メソッドを用いると、引数で与えた HTML 要素が子要素の末尾に追加されます。
問題コードに戻りましょう。
buildHTML
関数は、引数にとったレスポンスの items
配列をループさせて配列要素の数だけ <img>
要素を文字列の形で変数 html
に書き込みます。そして createElement
で作成した <div>
要素(変数 row
)の innerHTML
に追加します。
let html = '';
items.forEach(item => {
html += '<img class="column is-one-third" src="' + item.url + '" alt="" />';
});
row.innerHTML = html;
これで buildHTML
関数は完成です。
一行分の GIF 画像の一覧を表す HTML 要素を作成する関数ができたので、then
の中で呼び出し、結果を appendChild
で output
の子要素の末尾に追加します。
axios.get(API_URL + '?page=' + page)
.then(response => {
// HTMLを追加する
const row = buildHTML(response.data.items);
output.appendChild(row);
});
「もっと見る」ボタンをクリックするたびに GIF 画像が増えていく必要があるので innerHTML
ではなく appendChild
を使用します。
最後にレスポンスデータの last
が true
であれば、つまり最後のページであれば「もっと見る」ボタンは隠します。
// 最後のページであれば「もっと見る」ボタンは隠す
if (response.data.last) {
moreButton.classList.add('hidden');
}
今回のポイントは JavaScript による HTML の作成でした。実は React や Vue などのフレームワークを使用したモダンな開発手法ではあまり今回のように直接 HTML 要素を作成するコードは書かなくなるのですが、それでもフレームワークの裏側では間違いなく HTML 要素の作成や追加は行われているわけで、HTML 要素の操作は JavaScript の存在意義そのものと言えるでしょう。
以上、今回はもっと見る機能を実装するクイズを紹介しました。