エンターキーによる入力項目のフォーカスの移動をWeb系システムに実装してみる

今は懐かしきガラケーを使っていた人たちはこんな記憶はないだろうか?

「入力項目に入力カーソルを入れる際には、方向キーで入力したい場所を選んで決定キーを押す。」

スマホに取って代わられた今の時代、ガラケーのような動作を持たせる端末なんて、Webブラウザにはもう絶対に存在しないと思っている人も多いと思う。

 

だが、実際に物流や製造現場とかに入ってみるとそんなことは一切なく、タッチがサポートされているWindows CE7が搭載されたPDA端末(組込端末)においても、ガラケーのような感じで方向ボタンとナンバーボタンでメインの操作を行うなんてことはごくごく標準的な運用である。

その際にありふれたWeb系システム上での操作感で画面を使ってもらうとなると、主に入力項目へのフォーカスの際に実際のパネルボタンとの操作の乖離が生まれて非常に扱いづらい。

 

ましてや、現場でPDA端末を使う目的としては伝票(紙ベース)のバーコードだとか、QRコードだとかを入力する場合がほとんどで、これらを読み取った際にはおおよそ読み取りと同時にエンターキーの入力が走る。

そうすると必然的にフォームには送信のイベントが走ってしまうわけで、これだと他の入力値をついでに入力することもままならなくなる。

 

PDA端末で入力項目でエンターキーが押下された場合、それは次の項目にフォーカスを当てて然るべきである。

今回はそれに対応するために作ったJavaScript(jQuery)のコードを掲載したいと思う。

 

まず、基本的な要件と実装方法としては、

  • エンターのイベントを取得する。
  • エンターのイベントが検知された際に、下方向(I型操作画面の場合)を正方向として入力項目のカーソル移動とフォーカスを行う。
  • エンターはデータの取込みを行う決定ボタンだけを有効とする。
    ただし、決定ボタンをそもそもフォーカスしない場合は入力項目のフォーカスはローテーションさせる。
  • ついでにシフトキーによる逆方向へのローテーションもサポートする。
  • エンターキーが入力項目で自動入力されたとしてもフォームへの送信イベントの伝播は行わない。

だいたいこんな感じ。

 

とりあえず実装方法は書くコードを簡略化するために毎度おなじみjQueryを使用する。

ネイティブな書き方じゃないとヤな人は適宜変えてみると良いだろう(ニヤリ)

 

エンターはkeydownイベント時にコールバックされるe引数を取ればいい。

多くの他サイトさんでもよく掲載されているオーソドックスな形式で、単純にこんな形で行える。

$('.hoge').on( 'keydown', function(){
	if ( ( e.which && 13 == e.which ) || ( e.keyCode && 13 == e.keyCode ) ) {
		// 実行処理
	}
});

言わずもがな「// 実行処理」の部分にreturn false;を入れれば、フォームへのイベントの伝播は停止されるし、return true;を入れればイベントの伝播は通常通り行われる。

入力項目の種類を見て判断すれば、伝搬しない場合とする場合を分岐できる。(記事の最後のほうでまとめて書くフルコードに記載する。)

 

続いて、シフトキーの取得。

これはMDNさんのDocsあたりに掲載されていたevent.shiftKeyを利用する。

if ( event.shiftKey ) {
	// シフトキー有効(event.shiftKeyはtrueが入る)
} else {
	// シフトキー無し(event.shiftKeyはfalseが入る)
}

歴史が古いトップレベルイベントであるので、レガシーIEなどでも動く。

 

こいつらを合わせて書くとこうなる。

$('.hoge').on( 'keydown', function(){
	if ( event.shiftKey ) {
		if ( ( e.which && 13 == e.which ) || ( e.keyCode && 13 == e.keyCode ) ) {
		}
	} else {
		if ( ( e.which && 13 == e.which ) || ( e.keyCode && 13 == e.keyCode ) ) {
		}
	}
});

$('.hoge').on( 'keydown', function(){
	if ( ( e.which && 13 == e.which ) || ( e.keyCode && 13 == e.keyCode ) ) {
		if ( event.shiftKey ) {
		} else {
		}
	}
});

条件を二種に分けて書いたが、挙動はおおよそどちらでも同じ。(もしかしたらなんか違うかもしれんけど)

 

ここでキー系イベントの条件のラッパーの書き方の話になるが、「実際に入力するのはシフト+エンター入力だしコードもその方がわかりやすいやん」と解釈をする人なら前者条件で、「エンター入力が行われた時点でのシフトのイベントなんて単なるフラグやん」と解釈する人なら後者条件。

自分は考え方的に後者条件。ある時点でのevent.shiftKeyのフラグを別の関数側でも条件付ける事ができるため、エンターキーの入力時点でのシフトのフラグを後付けで見たほうが何かと都合が良い。

どちらにしても、eはkeydownイベントのコールバック、eventはトップレベルのイベントであることに留意すること。

逸れた話でコントロールキーが有効かを見たい場合は「event.ctrlKey」で可。

 

次にフォーカス移動の要件。

入力項目はフォーム内で連続するが、常に同じノードの中にあるとは限らない。

したがって、jqueryElement.next()とかjqueryElement.prev()では前次の入力項目を見ることはナンセンス。親要素を毎回たどって直列的に要素のDOMを上から見て判定するとか負荷がすごい。

 

そのため、こんな感じで取得と処理をフロー化。

$('.hoge').on( 'keydown', function(){ enter_focus( $(this) ); });
funtion enter_focus( now ){
	var fields = $('.hoge'); // .hogeのオブジェクトリスト
	var index = fields.index( now ); // キー入力イベントが走った際にフォーカスされている.hogeのインデックス(要素番号)
	var next_idx = 0;
	// ここでindexをベースにnext_idx(次要素番号)を割り出す
	fields.eq( next_idx ).focus();
}

フォーカスの必要な要素リストを取得して、エンター時にフォーカス中の要素のインデックスを取得する。

で、その後indexベースで次の要素番号を割り出して最終的にフォーカスさせる。

 

・・・これで全ての要件の原型の準備が整った。

 

以上の内容から、最終的にこういった感じでざっくりとフルコード化してみる。

(function($){

$.fn.enterFocus = function( options ) {
	var option = {
		fields: '.enter-focus'
	};
	var config = $.extend( option, options );

	var base = $(this);

	var enterFocus = (function() {
		bind: function( target, shift ) {
			// キーダウンからキーコード13(エンターキー)にバインド
			base.on( 'keydown', config.fields, function( e ) {
				if ( ( e.which && 13 == e.which ) || ( e.keyCode && 13 == e.keyCode ) ) {
					return enterFocus.cursor( $(this), event.shiftKey ); // メソッドからreturn値を参照
				}
			});
		},
		cursor: function( now, onShift ) {
			// 前次の要素の位置を割り出す処理
			// 【注意】Array.lengthを使うのでネイティブコードで必ずサポートしておくこと!
			var fields = base.find( config.fields ),
				index = fields.index( now ),
				next_idx = 0,
				isBtns = false;

			if ( 'BUTTON' == now.prop( 'tagName' ) || 'submit' == now.prop( 'type' ) || 'reset' == now.prop( 'type' ) ) {
				isBtns = true;
			}

			if ( onShift ) { // シフトキーが有効な際の処理 (逆方向へのローテーション)
				if ( 0 > index - 1 ) {
					// 現在フォーカスが最初要素の場合は最後の要素へ戻す
					next_idx = fields.length - 1;
				} else {
					// 前の要素
					next_idx = index - 1;
				}
			} else { // シフトキーなしの際の処理 (正方向へのローテーション)
				if ( isBtns ) {
					// 現在フォーカスがボタン系列のものは自分自身のインデックス
					next_idx = index;
				} else if ( fields.length - 1 < index + 1 ) {
					// 現在フォーカスが最終要素の場合は最初の要素へ戻す
					next_idx = 0;
				} else {
					// 次の要素
					next_idx = index + 1;
				}
			}

			// 実際にフォーカスを当てる
			this.setCursor( fields, next_idx );

			// 正方向ローテのときはボタン押下を有効にして返す
			if ( isBtns && ! onShift ) return true;

			return false;
		},
		setCursor: function( fields, index ) {
			if ( 'undefined' === typeof fields ) fields = null;
			if ( 'undefined' === typeof index ) index = null;
			if ( null != index ) index = 0;

			if ( null != fields ) {
				fields.eq( index ).focus();
			}
		}
	})();

	enterFocus.setCursor();
	enterFocus.bind();
};

})(jQuery);

 

jQueryプラグイン機能のスタートコードは以下のような感じ。

$( 'form' ).enterFocus({
	fields: 'enter-focus'
});

 

HTML構成は以下のように持たせる。

<form method="post" action="xxxxx.zzz">
	<p>商品コード: <input type="text" name="code" value="" class="enter-focus" /></p>
	<p>シリアル番号: <input type="text" name="serial" value="" class="enter-focus" /></p>
	<p>個数: <input type="text" name="quanty" value="" class="enter-focus" /></p>
	<p><input type="submit" name="send" value="送信" class="enter-focus" /></p>
</form>

 

これでWeb系システムでも入力項目をエンターキーでフォーカス移動させることができる。

 

とはいえ、これは毎度のことながらシステムに組み込む以上これだけの機能でもちろん完結しているわけではなく、実際にシステムで自分がコーディングしたものを抽出して使えそうな感じに記事用に書き換えただけである。

なので、基本的には目的に応じて改変することを推奨。

タブでのフォーカス移動に動きを一致させる必要があるのであれば、tabindexとかで見ても良いかもしれないし。

 

以上!

 

 

いつものように、あとがきともろもろ。

 

前の投稿からだいぶ日が空いたわけなんやけど、2月は本当に地獄のように忙しい日々だった。

完徹三回、アーリーリリース前の中間チェックでのプロブレムアワーとリストアラッシュ。

その上、確定申告用の整理やまとめ作業もやらにゃならんかったもんなぁ。オレノカラダハボドボドダ!!

 

ふとしたことで仕事中ブログを見返す機会があって、珍しくコメントをいただいていると気づいたのがなんとコメントがなされてから実に2週間超え、そして、超亀レス。

当該の記事にも書かせていただきましたが、ほんっっっとすみませんでしたああああああ!!!!

 

ようやくシステム開発にも区切りが一つ付き、ほんのわずかながら時間ができつつある。

な~んて、システム開発はまだまだ続くわけで忙しいことには変わりないんやけど・・・。

 

まぁ明後日から、次の開発再開予定の10日間余り先までサイト制作を入れる予定なので、そっちの方が遥かに気が重い(´・ω・`)

 

まぁ、同じ業種の別の分野を実際にダブルワークして気づいたことなのだが・・・。

 

Webシステム開発での動作改善・入出追加・修正等(一部)→それってどれだけ工数かかるんや?追加でお金ってどれくらい?

Webサイト制作でのパフォーマンス改善・入出追加・修正等(大量)→え?時間かかるん?まぁえーわ、とりあえずなんとかしてや。お金? 当 然 無 料 や ん な ?(にっこり)

 

双方、動的前提でやることの内容自体はほとんど変わらんのに露骨なこの温度差よ。

Webサイト制作を一つ為すたびに、明らかに加速度的に時間の浪費と負債がかさんでゆくのが草に合わせて芝生えるwww・・・いや、笑えないんやけどね。

まぁ、お仕事いただけてるうちはきちーんとこなすけどさ。

 

そういえば、作ったWebサイトのパフォーマンスが悪いと、スクリプトが悪いんじゃないかと外注元の人から飛んでくる事が少し増えたなぁ・・・。

いや、1000とか2000とかのノードに対して何かをしているわけじゃないからまったくもって関係ないのよね。

これって結局、見かけきれいに見せるためのWebフォントだとか画像のファイルサイズが9割9分で問題・・・と言うか全部それで解決しているお話。

前に務めていた会社でもたまにそういうことを上司から受けてたりしたけど、今のWeb従事者って見せかけ頭でっかちなんかね。

「サイトのパフォーマンスが悪いならスクリプトを疑う前によりサイズの大きい関連ファイルを調べる」って、学生レベルでもわかるようなごくごく初歩的なことなんやけど。

昔と違ってネットで情報がすぐ拾える分、何が問題なのかっていうのをざっくりとでも実物を調べる前に、「ネットで調べたからこう書いてあったから○○が悪い」とかって根拠に事欠いたことを結論づけられたら、フリでもそこから調べるしかないし、結果それでロスする時間が非常に多い。

自分も効率的なコードが書けているかと聞かれると、全然そんなことはないからあんまり言えた立場でもないんだけどね(´・ω・`)

カテゴリ

この記事のコメント

  1. こんにちは、エンターで移動からたどり着きました。
    コードを見ていて思ったのですが、
    var enterFocus = (function() {
    この部分は
    var enterFocus = {
    の間違いですか?
    エラーになりそうな感じだったのでコメント残します。

    楽しいサイトですね、色々拝見させていただきます(^^

    1. コメントいただきありがとうございます。

      うおぉ・・・!ご指摘いただいてハッとしました。
      確かにおおよそコメントの通り、掲載しているクロージャの閉じが間違っていたので修正しました。

      var enterFocus = (function() { ... })();

      以上が、正常な形になります。
      大変助かりました、ありがとうございます!

      ・・・やっぱり投稿の際にテスト無しで即興で書いちゃいけませんなぁ(´・ω・`)

      こんなコーダーの落書きみたいなブログでよろしければまた是非ともお願いします!

コメントを残す

メールアドレスが公開されることはありません。