サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Vue.js + Vuex入門 08: フォーカスをコントロールする


単一ファイルコンポーネントにVuexのStoreを加えてつくるTodoMVCアプリケーションのチュートリアルシリーズ「Vue.js + Vuex入門」の第8回は、いよいよアプリケーションを仕上げます。操作していて気になるのは、フォーカスの扱いです。不具合をふたつ直します。

01 ダブルクリックで編集しているテキストからフォーカスを外したら編集ボックスを閉じる

ひとつ目は、ダブルクリックで始めた項目の編集が、[enter]キーか[esc]キーを押さないかぎり終わらないことです。項目を編集中にしたまま、新たな項目を追加するフィールドにテキストが入力できてしまうのです(図001)。

図001編集を終えないまま新たな項目が入力できる

図001

編集ボックスコンポーネントsrc/components/TodoEdit.vueのテキスト入力フィールド(<input type="text">要素)からフォーカスを外したら、編集が終わるようにしなければなりません。イベントはblurです。つぎのように、編集キャンセルのリスナー関数(doneEdit())を与えました。これで、ダブルクリックで書き替えを始めた入力フィールドから、フォーカスを外すとテキストが編集されないまま終了します。

src/components/TodoEdit.vue

<template>
	<input id="edit" class="edit" type="text"

		@blur="cancelEdit">
</template>

02 編集ボックスにフォーカスを与える準備

まだひとつ不具合が残っています。項目をダブルクリックしただけで、編集ボックスには触れずに別の場所をクリックした場合は、blurイベントが起こらないので編集は終わりません。追加項目も入力できてしまいます。それは、項目をダブルクリックしただけでは、テキスト入力フィールドにフォーカスが入らないからです。それなら、ダブルクリックしたとき、そのコンポーネントの要素にフォーカスを移せばよいでしょうか。問題は、ダブルクリックするコンポーネント(src/components/TodoItem.vue)と編集するコンポーネント(src/components/TodoEdit.vue)が別で、切り替わることです。

そこで、クラスバインディングしたときの手を使いましょう(「Vue.js + Vuex入門 07」01「項目のダブルクリックで編集ボックスに切り替える」)。コンポーネントsrc/components/TodoList.vueは、つぎのように編集中の項目のオブジェクト(todo)を算出プロパティ(editedTodo)にもち、ふたつが一致する項目にクラス(editing)をバインディングしていました。

src/components/TodoList.vue

<template>
	<section class="main" v-show="todos.length" v-cloak>

		<ul class="todo-list">
			<li
				v-for="todo in filteredTodos"
				:class="{completed: todo.completed, editing: todo === editedTodo}"

			>

			</li>
		</ul>
	</section>
</template>

<script>

export default {

	computed: {
		...mapState([
			'todos',
			'editedTodo'
		]),

	},

};
</script>

フォーカスの操作をするのは、編集ボックスのコンポーネント(src/components/TodoEdit.vue)です。みずからの項目のオブジェクト(todo)はすでにpropsオプションにもっていますので、編集中の項目(editedTodo)を算出プロパティに加えてください。あとは、ふたつが一致したときフォーカスを与えればよいでしょう。

src/components/TodoEdit.vue

import {mapState} from 'vuex';
export default {

	props: {
		todo: Object
	},

	computed: {
		...mapState([
			'editedTodo'
		]),
	},
};

03 カスタムディレクティブを使う

そのとき用いるのが「カスタムディレクティブ」です。カスタムディレクティブには、v-につづけて任意の名前を定めます。以下のように、コンポーネントsrc/components/TodoEdit.vueのテンプレートのテキスト入力フィールド(<input type="text">要素)にディレクティブ(todo-focus)を加えました。与えた式の値はdirectivesオプションに定めるディレクティブと同名のメソッドの第2引数(binding)からvalueプロパティで取り出せます。第1引数(element)がディレクティブの与えられた要素です。

ディレクティブ名にハイフン(-)を含めたので、メソッド名の識別子としては定められません。そのため、文字列のキーを使いました。メソッドが呼び出されるのは、ディレクティブがはじめに要素に関連づけられたときと、そのあとは更新される都度その直前です(「関数による省略記法」参照)。

src/components/TodoEdit.vue

<template>
	<input id="edit" class="edit" type="text"
		v-todo-focus="todo == editedTodo"

		>
</template>

<script>

export default {

	directives: {
		'todo-focus'(element, binding) {
			if (binding.value) {
				element.focus();
			}
		}
	},

};
</script>

項目をダブルクリックすると、編集ボックスのテキスト入力フィールドにフォーカスが当たるようになりました。そして、フォーカスを外せば編集は終了です。手直しした編集ボックスのコンポーネントsrc/components/TodoEdit.vueの中身は、つぎのコード001のとおりです。でき上がったTodoMVCアプリケーションのすべてのモジュールについては、CodeSandboxに公開した以下のサンプル001をご参照ください。

コード001■テキスト入力フィールドのフォーカスをコントロールする

src/components/TodoEdit.vue

<template>
	<input id="edit" class="edit" type="text"
		v-todo-focus="todo == editedTodo"
		:value="todo.title"
		@input="onInput"
		@keypress.enter="doneEdit"
		@keyup.esc="cancelEdit"
		@blur="cancelEdit">
</template>

<script>
import {mapState} from 'vuex';
export default {
	name: 'TodoEdit',
	directives: {
		['todo-focus'](element, binding) {
			if (binding.value) {
				element.focus();
			}
		}
	},
	props: {
		todo: Object
	},
	data() {
		return {
			editedTitle: null
		};
	},
	computed: {
		...mapState([
			'editedTodo'
		]),
	},
	methods: {
		onInput(event) {
			this.editedTitle = event.target.value;
		},
		doneEdit(event) {
			this.editedTitle = event.target.value;
			this.$store.commit('doneEdit', this.editedTitle);
		},
		cancelEdit(event) {
			event.target.value = this.todo.title;
			this.$store.commit('cancelEdit');
		}
	}
};
</script>

サンプル001■vue-vuex-todo-mvc-08

Vue.js + Vuex入門


作成者: 野中文雄
作成日: 2019年11月12日


Copyright © 2001-2019 Fumio Nonaka.  All rights reserved.