本記事では、JavaScript 初心者の方向けに、ブラウザにおける JavaScript 開発の基礎について書いていきます。
最近は React や Vue.js などのフレームワークがスタンダードになって、「生の」JavaScript を扱うことは少なくなっているかもしれません。しかし、フレームワークの裏側で動く仕組みは当然同じです。技術トレンドが移り変わっても対応できる「基礎知識」を身につける、一助になれば幸いです。
- 少なくとも、HTML / CSS と、JavaScript の文法知識を前提としています。
- フロントエンド、つまりブラウザを実行環境とする JavaScript を扱います。Node.js などのサーバサイドの話題は含みません。
入力値の取得と更新
この記事ではフォームの扱いについて学びます。まずは、いろいろな入力欄の値を取得したり、更新する方法を紹介します。
ちなみにファイル入力欄は少し特殊なので、別項で説明します。
テキスト
input
や textarea
は、value
プロパティを介して値の取得や更新を行います。
<input type="text" value="123" />
const input = document.querySelector('input');
console.log(input.value); // -> "123"
input.value = "999";
値は必ず文字列で取得されるので、数値を扱いたい場合は注意しましょう。
ラジオボタン / チェックボックス
ラジオボタンやチェックボックスの場合は、name
でグルーピングされた入力欄のうち、選択されている値を取得したい、または選択値を変更したい、というニーズがあるでしょう。
<label for="foo">
<input id="foo" type="radio" name="example" value="aaa" /> foo
</label>
<label for="bar">
<input id="bar" type="radio" name="example" value="bbb" /> bar
</label>
選択中の値は、セレクター :checked
を用いて選択された input
を取得します。
const input = document.querySelector('input:checked');
console.log(input.value); // -> "BAR"
上はラジオボタンの例ですが、チェックボックスの場合は複数選択されている場合があるので、querySelectorAll
で取得するとよいでしょう。
const inputs = [...document.querySelectorAll('input:checked')];
const values = inputs.map(input => input.value);
console.log(values); // -> ex. ["FOO", "BAR"]
選択状態を変更するには、要素の checked
プロパティを更新します。
const input = document.querySelector('input[value="FOO"]');
input.checked = true; // チェックが付く
// 逆にチェックを外す場合は false を代入する
セレクトボックス
択一のセレクトボックスの場合は、value
プロパティで選択値を取得・更新できます。
<select>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
const select = document.querySelector('select');
console.log(select.value); // -> "1"
select.value = "2";
複数選択の場合は、selected
プロパティが true
な option
要素を抽出することで選択値を取得します。
<select multiple>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
const select = document.querySelector('select');
const options = [...select.querySelectorAll('option')];
const values = options
.filter(option => option.selected)
.map(option => option.value);
// -> ex. ["1", "3"]
選択値の変更も同様に、option
要素の selected
を更新します。この方法は択一の場合にももちろん使えます。
const select = document.querySelector('select');
const option = select.querySelector('option[value="3"]');
option.selected = true;
主な関連イベント
フォームにまつわる、よく使う / 目にするであろうイベントを紹介します。
入力欄のイベント
change
change
イベントは、入力欄の値が変更が確定したときに発火します。
以下は、入力値が変更されたときにその値を取得するコード例です。イベントオブジェクトの target
には、addEventListener
した要素が格納されているので、さらに value
プロパティから入力値を参照しています。
const input = document.querySelector('input');
input.addEventListener('change', e => {
console.log(e.target.value); // -> 入力値
});
input
input
イベントは、入力値が変更されるたびに発火します。
change
イベントと似ているのですが、入力欄の種類によって、発火のタイミングが異なる場合があります(以下は Firefox で試しています)。
type
がtext
やemail
などのテキスト入力を行うinput
では、文字を打つごとにinput
イベントが発火し、エンターキーを押すか、フォーカスを外すとchange
イベントが発火します。type
がnumber
のinput
は、基本的にはtext
と同様ですが、左端の up / down のコントロールを押したときはinput
とchange
イベントが同時に発火します。textarea
では、文字を打つごとにinput
イベントが発火し、フォーカスを外すとchange
イベントが発火します(エンターキーが改行となるため)。- ラジオボタンやセレクトボックスなど、その他の入力欄では、
input
とchange
イベントは同時に発火しました。
focus / blur
focus
イベントと blur
イベントは、それぞれ入力欄にフォーカスが移動したときと、それが外れたときに発火します。
keydown
keydown
イベントは、キーボードのキーが押されたときに発火します。
よくあるユースケースは、特定のキーが押されたときになにかする、というパターンです。以下は、たとえばエンターキーが押されたときに検索するサンプルです。
const form = document.querySelector('form');
const input = document.querySelector('input');
input.addEventListener('keydown', e => {
// エンターキーはキーコードが 13 と決まっている
if (e.keyCode === 13) {
form.submit();
}
});
フォームのイベント
submit
submit
イベントは、フォームが送信される直前に発火します。
独自の送信ロジックを組みたい場合などは、preventDefault
で送信をキャンセルできます。
const form = document.querySelector('#the-form');
form.addEventListener('submit', e => {
e.preventDefault(); // 送信をキャンセルする
// 後続処理...
});
ファイル入力
ファイルの取得
添付されたファイルは、input
要素の files
プロパティから取得します。ファイルの情報は、FileList
オブジェクトとして取得されます。
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', e => {
console.log(e.target.files); // -> FileList オブジェクト
});
FileList
オブジェクトは、添付されたファイルの情報を配列に似たコレクションとして保持しています。ファイルは複数添付もできるため、そのようなデータ構造になっているのだと思います。単一添付の場合はインデックスを用いてファイル情報を取り出します。
e.target.files[0]
ファイルの更新
ファイル入力欄の場合は、プログラムから特定のファイルを添付させることはできません。これはセキュリティ上の制約だと思われます。
ただ、よく困るのが、入力 → 確認 → 完了 のような入力フローを作ったときに、確認画面から入力画面に戻ったときに、ファイルが消失しているパターンだと思います。他の入力欄は入力値を後から当てはめられるのですが、ファイルの場合はそれができないためです。
一時的な領域にファイルをアップロードするとか、入力フローをシングルページにして 入力 ←→ 確認 でページのリロードを発生させないとか、そういった工夫で解決しましょう。
ファイルのクリア
ファイルの添付はできませんが、クリアすることはできます。
input.value = null;
プレビュー実装例
画像ファイルを添付したときに、プレビューを表示する機能は、ありがちだと思われるので、実装例を載せておきます。
プログラムからファイルの読み込みを行う FileReader
オブジェクトを用いて実装します。
const input = document.querySelector('input[type="file"]');
const output = document.querySelector('output');
input.addEventListener('change', e => {
const files = e.target.files; // FileList を取得する
if (files.length === 0) return false; // 添付がなければ処理終了
const file = files[0]; // ここでは単一添付を想定する
// 添付されたファイルが画像以外であれば処理終了
if (file.type.indexOf('image') === -1) return false;
const reader = new FileReader();
// FileReader によるファイルの読み込みが完了したときに実行される関数を登録する
reader.addEventListener('load', e => {
const imageUrl = e.target.result; // 読み込み結果
const img = document.createElement('img'); // img 要素を生成
img.setAttribute('src', imageUrl);
const oldImg = output.querySelector('img');
if (oldImg) oldImg.remove(); // すでに img 要素があれば削除しておく
// output 要素の子に img 要素を挿入する
output.insertAdjacentElement('beforeend', img);
});
// ファイルをデータURLとして読み込む
// 読み込みが完了したら上で onload に登録した関数が実行される
reader.readAsDataURL(file);
});
ファイルが複数の場合の実装例や、他にもファイルの読み込みにまつわる話題は、以下のページにもまとまっています。
JavaScript でのローカル ファイルの読み込み - HTML5 Rocks
フォームバリデーション
フロントエンドのフォームバリデーションは、独自実装もできますが、HTML / JavaScript の標準仕様としても定義があります。
紹介しようと思いましたが、すでに以下のページに網羅的にまとまっていたので、リンクを載せるに留めます。
クライアント側のフォームデータ検証 - ウェブ開発を学ぶ | MDN
ブラウザ側のバリデーションがほしいけど独自実装するほどでもない、という場合は、こちらを参考にしてみるのもいいかもしれません。
非同期送信
最後に、フォームを非同期で送信する方法(いわゆる Ajax)を紹介します。
Fetch API
フォームを非同期で送信する方法として、JavaScript 標準の Fetch API が定義されています。
こちらも、以下の MDN ページに網羅的にまとまっているので、こちらを必要に応じて読みつつ、使い始められると思います。
ちなみに IE には実装されていないので、別途ポリフィルが必要です。
外部ライブラリ
標準の fetch
ではなく外部ライブラリを使うなら、axios がオススメです。
fetch
は必要最低限の機能だけが用意されている軽量さが魅力でもありますが、たいてい結局は、
- クエリパラメータを生成して付加する
- リクエスト発行の前後に特定の処理を挟みたい
などの処理を加えてラップした独自モジュールを作ることになるので、個人的には、ある程度の規模のアプリになりそうであれば、始めから axios
を選択することが多いです。
以上、本記事では、JavaScript の超基礎講座として、フォームを扱う方法を見ていきました。