2020.09.27

JavaScript超基礎講座!フォームの扱いを学ぶ


本記事では、JavaScript 初心者の方向けに、ブラウザにおける JavaScript 開発の基礎について書いていきます。

最近は React や Vue.js などのフレームワークがスタンダードになって、「生の」JavaScript を扱うことは少なくなっているかもしれません。しかし、フレームワークの裏側で動く仕組みは当然同じです。技術トレンドが移り変わっても対応できる「基礎知識」を身につける、一助になれば幸いです。

  • 少なくとも、HTML / CSS と、JavaScript の文法知識を前提としています。
  • フロントエンド、つまりブラウザを実行環境とする JavaScript を扱います。Node.js などのサーバサイドの話題は含みません。

入力値の取得と更新

この記事ではフォームの扱いについて学びます。まずは、いろいろな入力欄の値を取得したり、更新する方法を紹介します。

ちなみにファイル入力欄は少し特殊なので、別項で説明します。

テキスト

inputtextarea は、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 プロパティが trueoption 要素を抽出することで選択値を取得します。

<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 で試しています)。

  • typetextemail などのテキスト入力を行う input では、文字を打つごとに input イベントが発火し、エンターキーを押すか、フォーカスを外すと change イベントが発火します。
  • typenumberinput は、基本的には text と同様ですが、左端の up / down のコントロールを押したときは inputchange イベントが同時に発火します。
  • textarea では、文字を打つごとに input イベントが発火し、フォーカスを外すと change イベントが発火します(エンターキーが改行となるため)。
  • ラジオボタンやセレクトボックスなど、その他の入力欄では、inputchange イベントは同時に発火しました。

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 ページに網羅的にまとまっているので、こちらを必要に応じて読みつつ、使い始められると思います。

Fetch の使用 - Web API | MDN

ちなみに IE には実装されていないので、別途ポリフィルが必要です。

外部ライブラリ

標準の fetch ではなく外部ライブラリを使うなら、axios がオススメです。

fetch は必要最低限の機能だけが用意されている軽量さが魅力でもありますが、たいてい結局は、

  • クエリパラメータを生成して付加する
  • リクエスト発行の前後に特定の処理を挟みたい

などの処理を加えてラップした独自モジュールを作ることになるので、個人的には、ある程度の規模のアプリになりそうであれば、始めから axios を選択することが多いです。


以上、本記事では、JavaScript の超基礎講座として、フォームを扱う方法を見ていきました。