はじめに
- Trelloのチェックリストは、フォーカスが外れたときに編集内容がキャンセルされる
- 単純にinputのblurイベントをコールするのではなく、関連機能にフォーカスがある場合はキャンセルされない
- 対象コンポーネント(textbox、OKボタン、キャンセルボタン)にフォーカスがある場合は編集内容はキャンセルされない
- それ以外の項目に移動した場合は、キャンセルする
- この機能を自作してみる
成果物
仕組み
- focusin/focusoutイベントがバブリングする性質を利用
- 対象コンポーネントの最上位要素にイベントを登録
- フォーカスが移動するたびに、focusin/focusoutが発生
- つまり、focusinが発生しない場合は、キャンセルとする
- focusout -> focusinは連続して発生するため、focusinが発生しないか、少し遅らせて判定
- focusout時にキャンセル処理を遅延実行し、focusin時に遅延実行を無効化
- focusinが実行されなければ、そのままキャンセル処理が実行される
- focusout -> focusinは連続して発生するため、focusinが発生しないか、少し遅らせて判定
コンポーネント実装の抜粋
- CSSは省略
- Vueで実装
(上記画像は実装中の自作アプリのため、下記のコードの見た目は異なります。)
<template> <div @focusout="handleFocusout" @focusin="handleFocusin"> <div v-if="!editMode"> <label> <span>{{ title }}</span> </label> <button @click.left="onEditMode">Edit</button> <button @click.left="deleteData">X</button> </div> <div v-if="editMode"> <input ref="inputtext" v-model="title" type="text" > <button @click="update">O</button> <button @click="cancel">X</button> </div> </div> </template> <script> const defaultText = '1234567890' export default { data () { return { title: defaultText, editMode: false, focusTimerId: null } }, destroyed () { clearTimeout(this.focusTimerId) }, methods: { onEditMode () { this.editMode = true this.$nextTick(() => { this.$refs.inputtext.focus() }) }, cancel () { this.editMode = false this.title = defaultText this.$emit('cancel') }, update () { this.editMode = false this.$emit('update', this.title) }, deleteData () { this.$emit('delete') }, handleFocusout () { if (!this.editMode) { return } // コンポーネントからフォーカスアウト時にキャンセル // NOTE: focusout/focusinのバブリングを利用 this.focusTimerId = setTimeout(() => this.cancel(), 100) }, handleFocusin () { if (!this.editMode) { return } clearTimeout(this.focusTimerId) } } } </script>