サイトトップ

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

HTML5テクニカルノート

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


Vue.js + CLI入門 05: チェックをまとめてオン/オフしたり削除する」(以下「Vue.js + CLI入門 06」)で、Todoリストはほぼでき上がりました。けれど、まだ気になるところがあります。それは、フォーカスの扱いです。ふたつの不具合を直して仕上げます。

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

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

図001■編集を終えないまま新たな項目が加えられる

図001

テキスト入力フィールド(<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 + CLI入門 06」01「項目のダブルクリックで編集のテキスト入力フィールドを出す」)。コンポーネントsrc/components/TodoList.vueは、つぎのように編集中の項目のオブジェクト(todo)をdataオプションのプロパティ(editedTodo)にもち、ふたつが一致する項目にv-bind:class(省略記法:class)でCSSのクラスをバインディングしていました。

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="{editing: todo == editedTodo}">

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

<script>

export default {

	data() {
		return {
			editedTodo: null
		};
	},
	methods: {

		editTodo(todo) {
			this.editedTodo = todo;
		},
		doneEdit(todoTitle) {

			this.editedTodo = null;
		},
		cancelEdit() {
			this.editedTodo = null;
		}
	}
}
</script>

フォーカスの操作は、項目編集のコンポーネント(src/components/TodoEdit.vue)に委ねます。そのためには、つぎのように編集中の項目が納められたオブジェクトのプロパティ(editedTodo)をリスト表示のコンポーネント(src/components/TodoList.vue)からバインディングして、項目編集のコンポーネントがpropsで受け取っておかなければなりません。自身の項目のオブジェクト(todo)はすでにバインディング済みですので、あとはふたつが一致したときフォーカスを与えればよいでしょう。

src/components/TodoList.vue

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

		<ul class="todo-list">
			<li v-for="todo in filteredTodos"

				>

				<todo-edit
					:todo="todo"
					:editedTodo="editedTodo"

					>
				</todo-edit>
			</li>
		</ul>
	</section>
</template>

src/components/TodoEdit.vue

<script>
export default {

	props: {
		todo: Object,
		editedTodo: Object
	},

}
</script>

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

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

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

src/components/TodoEdit.vue

<template><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のとおりです。リスト表示コンポーネント(src/components/TodoList.vue)については、前項02「ダブルクリックで編集するテキスト入力フィールドにフォーカスを与える」でデータバインディングをひとつ加えただけですので省きます。アプリケーションのすべてのファイルについては、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>
export default {
	name: 'TodoEdit',
	directives: {
		['todo-focus'](element, binding) {
			if (binding.value) {
				element.focus();
			}
		}
	},
	props: {
		todo: Object,
		editedTodo: Object
	},
	data() {
		return {
			editedTitle: null
		};
	},
	mounted() {
		this.editedTitle = this.todo.title;
	},
	methods: {
		onInput(event) {
			this.editedTitle = event.target.value;
		},
		doneEdit(event) {
			this.editedTitle = event.target.value;
			this.$emit('done-edit', this.editedTitle);
		},
		cancelEdit(event) {
			event.target.value = this.todo.title;
			this.$emit('cancel-edit');
		}
	}
}
</script>

サンプル001■vue-todo-mvc-07

Vue.js + CLI入門


作成者: 野中文雄
更新日; 2019年09月09日 サンプル001を追加。
更新日: 2019年03月24日 コードと本文の一部を修正。
作成日: 2019年03月22日


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