HTML5テクニカルノート
Vue.js + CLI入門 06: 項目のテキストをダブルクリックで再編集する
- ID: FN1902001
- Technique: HTML5 / ECMAScript 2015
- Library: Vue.js 2.6.6
「Vue.js + CLI入門 05: チェックをまとめてオン/オフしたり削除する」(以下「Vue.js + CLI入門 05」)では、項目にまとめてチェックをつけたり、チェック済みの項目すべてを削除できるようにしました。今回は、ひとつの項目を削除してから改めて追加することなく、すでに入力した項目のテキストを書き替えられるようにします。
01 項目のダブルクリックで編集のテキスト入力フィールドを出す
項目を書き替えるときもボタンは使わず、項目テキスト(<label>
要素)のダブルクリックで進めます。リスナーを定めるイベントv-on:dblclick
(省略記法@dblclick
)です[*1]。リスナーメソッド(editTodo()
)は、つぎのように$emit()
で項目のオブジェクト(todo
)をイベント(edit-todo
)ともに親コンポーネントに送ります。
src/components/TodoItem.vue<template> <div class="view"> <label @dblclick="editTodo"> {{todo.title}} </label> </div> </template> <script> export default { methods: { editTodo() { this.$emit('edit-todo', this.todo); } } } </script>
ダブルクリックのイベント(edit-todo
)が親コンポーネント(TodoList.vue
)に渡されると、受け取った項目オブジェクトを以下のようにリスナーメソッド(editTodo()
)でプロパティ(editedTodo
)に定めます。すると、その項目の親要素(<li>
)にはv-bind:class
(省略記法:class
)構文で新たなクラス(editing
)がバインディングされるのです(「HTMLクラスのバインディング」参照)。なお、新たに加えた項目編集用の子コンポーネント(TodoEdit.vue
)は、のちほどつくります。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" :class="{completed: todo.completed, editing: todo == editedTodo}"> <todo-item @edit-todo="editTodo"> </todo-item> <todo-edit :todo="todo"> </todo-edit> </li> </ul> </section> </template> <script> import TodoEdit from './TodoEdit.vue'; export default { components: { TodoEdit }, data() { return { editedTodo: null }; }, methods: { editTodo(todo) { this.editedTodo = todo; } } } </script>
親アプリケーション(App.vue
)の<style>
に@import
したindex.css
には、editing
クラスの中の項目の子コンポーネント(TodoItem.vue
)に与えたview
クラスに対して、つぎのように表示を隠す定めが加えてあります。つまり、ダブルクリックした項目は消えるということです。
https://unpkg.com/todomvc-app-css@2.2.0/index.css.todo-list li.editing .view { display: none; }
src/components/TodoItem.vue<template> <div class="view"> </div> </template>
項目を編集するコンポーネントの定めはつぎのとおりです。項目テキストが<input>
要素のvalue
属性にバインディングされて表示されます。クラスedit
の要素は、index.css
により以下のようにはじめは表示していません。けれど、項目がダブルクリックされて親要素にediting
クラスがバインディングされると表れるのです(図001)。
src/components/TodoEdit.vue<template> <input id="edit" class="edit" type="text" :value="todo.title"> </template> <script> export default { name: 'TodoEdit', props: { todo: Object } } </script>
https://unpkg.com/todomvc-app-css@2.2.0/index.css.todo-list li.editing .edit { display: block; } .todo-list li .edit { display: none; }
図001■項目をダブルクリックすると編集用のテキスト入力フィールドが表れる
[*1] Vue.jsサイトの「イベントハンドリング」には、dblclick
イベントが載っていません。けれど、JavaScriptネイティブのイベントはサポートされます(「Why not add v-on:doubleClick」参照)。↩
02 [enter]キーで編集を確定する
編集した項目テキストはモジュールsrc/components/TodoEdit.vue
のテンプレートに、以下のように@input
イベントのリスナーメソッド(onInput()
)を加え、プロパティ(editedTitle
)に反映します。項目の追加と同じく、編集の確定は[enter]キーです。@keypress.enter
イベントで、リスナーメソッド(doneEdit()
)から親のコンポーネントに$emit()
でイベントと新たな項目テキストを送ります。また、編集する項目のテキストはmounted()
で、プロパティに与えました。
イベント(done-edit
)を受け取ったモジュールsrc/components/TodoList.vue
は、リスナーメソッド(doneEdit()
)でその項目(editedTodo
)のテキスト(title
)を改めます。これで、編集したテキストが[enter]キーで差し替わるようになりました(図002)。なお、受け取ったテキストが空または空白スペースのみのときは、項目がデータから除かれます。
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" @done-edit="doneEdit"> </todo-edit> </li> </ul> </section> </template> <script> export default { methods: { doneEdit(todoTitle) { if (!this.editedTodo) { return; } const title = todoTitle.trim(); if (title) { this.editedTodo.title = title; } else { this.removeTodo(this.editedTodo); } this.editedTodo = null; } } } </script>
src/components/TodoEdit.vue<template> <input id="edit" class="edit" type="text" :value="todo.title" @input="onInput" @keypress.enter="doneEdit"> </template> <script> export default { 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); } } } </script>
図002■編集したテキストが[enter]キーで差し替わる
03 [esc]キーで編集を中止する
編集したテキストを[enter]で確定せず、[esc]キーを押したときはとり止めることにしましょう。モジュールsrc/components/TodoEdit.vue
のテンプレートに加えるのは、以下のような@keyup.esc
イベントのリスナーメソッド(cancelEdit()
)です。<input type="text">
要素のテキストは項目オブジェクト(todo
)のタイトルに戻し、親コンポーネントに取り消しのイベント(cancel-edit
)を送ります。
イベントを受け取ったモジュールsrc/components/TodoList.vue
がリスナーメソッド(cancelEdit()
)で行うのは、編集項目のプロパティ(editedTodo
)をnull
にすることだけです。データはそのまま変えなくてよく、表示する子コンポーネント(TodoItem.vue
)がクラスバインディングで切り替わって表れます。
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 @cancel-edit="cancelEdit"> </todo-edit> </li> </ul> </section> </template> <script> export default { methods: { cancelEdit() { this.editedTodo = null; } } } </script>
src/components/TodoEdit.vue<template> <input id="edit" class="edit" type="text" @keyup.esc="cancelEdit"> </template> <script> export default { methods: { cancelEdit(event) { event.target.value = this.todo.title; this.$emit('cancel-edit'); } } } </script>
ここまで書き加えたリスト表示(src/components/TodoList.vue
)とリスト項目(src/components/TodoController.vue
)および項目編集の各モジュールファイルの中身は、つぎのコード001にまとめたとおりです。他のモジュールも含めたアプリケーションのファイルのコードは、CodeSandboxに公開した以下のサンプル001をご参照ください。
コード001■項目テキストをダブルクリックで編集する
<template>
<section class="main" v-show="todos.length" v-cloak>
<input id="toggle-all" class="toggle-all" type="checkbox"
:value="allDone"
:checked="allDone"
@change="onInput">
<label for="toggle-all"></label>
<ul class="todo-list">
<li v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
:class="{completed: todo.completed, editing: todo == editedTodo}">
<todo-item
:todo="todo"
@remove-todo="removeTodo"
@done="done"
@edit-todo="editTodo">
</todo-item>
<todo-edit
:todo="todo"
@done-edit="doneEdit"
@cancel-edit="cancelEdit">
</todo-edit>
</li>
</ul>
</section>
</template>
<script>
import TodoItem from './TodoItem.vue';
import TodoEdit from './TodoEdit.vue';
export default {
name: 'TodoList',
components: {
TodoItem,
TodoEdit
},
props: {
todos: Array,
filteredTodos: Array,
allDone: Boolean
},
data() {
return {
editedTodo: null
};
},
methods: {
removeTodo(todo) {
this.$emit('remove-todo', todo);
},
done(todo, completed) {
this.$emit('done', todo, completed);
},
onInput() {
this.$emit('allDone', !this.allDone);
},
editTodo(todo) {
this.editedTodo = todo;
},
doneEdit(todoTitle) {
if (!this.editedTodo) {
return;
}
const title = todoTitle.trim();
if (title) {
this.editedTodo.title = title;
} else {
this.removeTodo(this.editedTodo);
}
this.editedTodo = null;
},
cancelEdit() {
this.editedTodo = null;
}
}
}
</script>
<style scoped>
[v-cloak] {
display: none;
}
</style>
<template>
<div class="view">
<input
type="checkbox" class="toggle"
:value="todo.completed"
:checked="todo.completed"
@change="onInput">
<label @dblclick="editTodo">
{{todo.title}}
</label>
<button
class="destroy"
@click="removeTodo">
</button>
</div>
</template>
<script>
export default {
name: 'TodoItem',
props: {
todo: Object
},
methods: {
removeTodo() {
this.$emit('remove-todo', this.todo);
},
onInput() {
this.$emit('done', this.todo, !this.todo.completed);
},
editTodo() {
this.$emit('edit-todo', this.todo);
}
}
}
</script>
<template>
<input id="edit" class="edit" type="text"
:value="todo.title"
@input="onInput"
@keypress.enter="doneEdit"
@keyup.esc="cancelEdit">
</template>
<script>
export default {
name: 'TodoEdit',
props: {
todo: 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-06
Vue.js + CLI入門
- Vue.js + CLI入門 01: リスト項目の表示と追加
- Vue.js + CLI入門 02: リスト項目の削除とスタイル変更
- Vue.js + CLI入門 03: データの計算処理とローカルへの保存
- Vue.js + CLI入門 04: リスト表示する項目を選び出して切り替える
- Vue.js + CLI入門 05: チェックをまとめてオン/オフしたり削除する
- Vue.js + CLI入門 06: 項目のテキストをダブルクリックで再編集する
- Vue.js + CLI入門 07: フォーカスをコントロールする
- Vue.js + CLI入門 08: 要素にアニメーションを加える
作成者: 野中文雄
更新日; 2019年09月08日 サンプル001を追加。
更新日: 2019年03月24日 ブラウザの違いへの対応も含めて、本文とコードを修正。
更新日: 2019年03月18日 04「ブラウザの違いによる問題に対応する」を追加。
作成日: 2019年02月28日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.