jQuery で入力値を取得する — .val() の使い分けとリアルタイム取得
フォームの入力値を取る、というだけなら .val() で終わりです。
実際に手が止まるのはその先で、「チェックボックスだけ取り方が違う」「入力中にリアルタイムで反映したい」
「日本語を打つと変換途中の文字が流れ込んでくる」あたりです。
そこまで含めて1本に並べます。
先に結論
| やりたいこと | 書き方 |
|---|---|
| テキスト・テキストエリアの値 | $('#foo').val() |
| セレクトボックスの値 | $('#foo').val()(複数選択なら配列) |
| チェックされたチェックボックス | $('[name="foo"]:checked').map((i, e) => e.value).get() |
| 選択されたラジオボタン | $('[name="foo"]:checked').val() |
| 確定したタイミングで取る | $('#foo').on('change', fn) |
| 入力されるたびに取る | $('#foo').on('input', fn) |
| 日本語の変換確定まで待つ | compositionstart / compositionend で囲う |
基本は .val()
input でも textarea でも、値は .val() で取れます。
<input type="text" id="name" class="form-name" name="user_name" value="山田太郎">
$('#name').val(); // id で取る
$('.form-name').val(); // class で取る
$('[name="user_name"]').val(); // name 属性で取る
どれでも結果は同じです。ただし .val() は
マッチした要素のうち最初の1つの値しか返しません。
class で複数拾ってしまっている場合、2つ目以降は無視されます。意図せず複数マッチしていないかだけ気をつけてください。
設定するときは引数を渡します。
$('#name').val('新しい値');
$('#name').val(''); // 空にする
部品によって変わるところ
セレクトボックス
単一選択ならそのまま .val() で選択中の value が返ります。
multiple が付いている場合は配列です。
$('#single').val(); // "cakephp"
$('#multi').val(); // ["cakephp", "laravel"]
何も選ばれていないときの戻り値には、jQuery のバージョンで差があります。
jQuery 3.0 以降は空配列、それ以前は null が返ります。
古いコードを 3 系に上げたときに .length で落ちるのはたいていここです。
// どちらのバージョンでも安全に書くなら
var values = $('#multi').val() || [];
チェックボックス
チェックボックスに .val() を使うと、チェックの有無に関係なく
その要素の value 属性が返ってきます。欲しいのはたいてい「チェックされているものだけ」なので、
:checked で絞ります。
// チェックされているか
$('#agree').prop('checked'); // true / false
// チェックされている値を全部集める
var checked = $('[name="lang"]:checked').map(function () {
return this.value;
}).get(); // ["php", "js"]
.map() が返すのは jQuery オブジェクトなので、
素の配列にするには .get() が要ります。ここを忘れると
join() などが思ったとおりに動きません。
チェック状態の取得・変更は .attr('checked') ではなく .prop('checked') を使います。
attr は HTML に書かれた初期値を見るもので、ユーザーが操作した後の現在の状態とは別物です。
ラジオボタン
同じグループの中から選択中のものを取ります。
$('[name="plan"]:checked').val(); // 選択中の value
$('[name="plan"]:checked').length; // 0 なら未選択
未選択のときは .val() が undefined を返します。
必須チェックをするなら .length を見るほうが確実です。
入力されたら即取りたい:change と input
2つの違い
この2つは発火するタイミングが違います。
| イベント | 発火するタイミング | 向いている用途 |
|---|---|---|
change | 値が確定したとき。テキスト系はフォーカスが外れた時点、セレクトやチェックボックスは操作した時点 | セレクトの切り替え、入力完了後のバリデーション |
input | 値が変わるたび。1文字打つたびに発火する | 文字数カウント、検索のインクリメンタル表示、プレビュー |
どちらを使うか
セレクトボックスなら change で十分です。選んだ瞬間に確定するからです。
$('#framework').on('change', function () {
$('#result').text($(this).val());
}).trigger('change'); // 初期表示にも反映させる
一方テキスト入力で change を使うと、入力中はうんともすんとも言いません。
フォーカスが外れてはじめて動きます。1文字ごとに反映したいなら input です。
1文字ごとに反映する
$('#memo').on('input', function () {
$('#count').text($(this).val().length + '文字');
});
input には .change() のようなショートハンドメソッドがないので、
.on('input', ...) の形で書きます。
input イベントが普及する前は、keyup で拾って前回の値と比較する、という書き方をしていました。
いまは input がどのブラウザでも使えるので、その必要はありません。
keyup だとマウスでの貼り付けやサジェストからの入力を取りこぼすので、
既存コードで見かけたら input に寄せるほうが素直です。
日本語入力で詰まるところ
何が起きるか
ここが input の実務上の罠です。
日本語を入力しているとき、変換を確定する前の文字でも input は発火します。
「にほんご」と打って変換するだけで、n → に → にほ …… と、
確定前の文字列が何度も流れ込んできます。
文字数カウントくらいなら見た目が揺れるだけで済みますが、
入力のたびにサーバへ検索を投げる作りだと、確定前の読みがそのままリクエストになります。
確定を待つ書き方
compositionstart と compositionend で挟んで、
変換中かどうかを自分で持ちます。
var composing = false;
$('#search')
.on('compositionstart', function () {
composing = true;
})
.on('compositionend', function () {
composing = false;
update(this.value); // 確定した時点で1回だけ走らせる
})
.on('input', function () {
if (composing) { return; } // 変換中は何もしない
update(this.value);
});
function update(value) {
$('#result').text(value);
}
ポイントは compositionend 側でも update() を呼んでいるところです。
変換を確定した瞬間のイベント順序はブラウザによって差があり、
compositionend の後に input が来ないケースがあります。
片側だけに書くと、確定しても反映されないブラウザが出ます。
逆に、変換中の文字も含めて拾いたい(インクリメンタル検索で読みの段階から候補を出したい、など)なら この処理は不要です。要件次第で、どちらが正しいかは変わります。 どちらにしても、日本語を扱うフォームでは一度は確認しておく挙動です。
後から追加した要素
Ajax やテンプレートで後から差し込んだ要素には、先に書いたイベントが付きません。 イベントを登録した時点でその要素が存在していないからです。
// 動かない(登録時に .dynamic が無い)
$('.dynamic').on('input', handler);
// 動く(親に登録して、.dynamic のときだけ実行する)
$('#form-area').on('input', '.dynamic', handler);
委譲先は document でも動きますが、
イベントが毎回ドキュメント全体まで登っていくので、
できるだけ近い親要素を指定したほうが素直です。
jQuery なしで同じことをやる
ここまでの内容は、素の JavaScript でもほぼ同じ行数で書けます。
const memo = document.querySelector('#memo');
memo.addEventListener('input', () => {
document.querySelector('#count').textContent = memo.value.length + '文字';
});
// チェックされている値を集める
const checked = [...document.querySelectorAll('[name="lang"]:checked')]
.map(el => el.value);
:checked のような擬似クラスは querySelectorAll でもそのまま使えます。
compositionstart / compositionend の扱いも同じです。
判断としては、すでに jQuery が入っている既存コードなら無理に外す必要はなく、 新規でこの程度の処理のために読み込むなら不要、というところです。 jQuery を外すこと自体が目的になると、DOM 操作以外の部分(Ajax の書き換えなど)まで巻き込んで かえって時間がかかります。