Vue.js でとても便利な v-model
ですが contenteditable
な要素では使えません。 v-model
は内部的には v-bind:value
と v-on:input
をまとめてやってくれているからです。つまり input
要素じゃない contenteditable
では効かないという感じです。
そこで自前でイベントを使って雑に書くとこんな風に書いてしまいますが、これには問題があります。入力するたびにキャレットが文字列の先頭に飛んでしまいます。これは v-text
が更新されるため自身も render が走ってしまうのが原因です。かといって v-once
を付けてしまうと、今度は自身を変更するすべがなくなってしまいます。
<template>
<div>
<div contenteditable="true" v-text="content" @input="sync"></div>
<div>{{ content }}</div>
</div>
</template>
<script>
import Vue from 'vue'
export default Vue.extend({
name: 'HelloWorld',
data () {
return {
content: 'sample text'
}
},
methods: {
sync (e) {
this.content = e.target.innerText
}
}
})
</script>
回避方法は至って単純で、もう一つ contenteditable
用の変数を用意すること。
<template>
<div class="hello">
<div contenteditable="true" v-text="innerContent" @input="sync"></div>
<div v-html="content"></div>
</div>
</template>
<script>
import Vue from 'vue'
export default Vue.extend({
name: 'HelloWorld',
data () {
return {
innerContent: '',
content: 'sample text'
}
},
methods: {
sync (e) {
this.content = e.target.innerHTML
}
},
mounted() {
this.innerContent = this.content
}
})
</script>
ついでにコンポーネントにしておきます。
<template>
<div contenteditable="true" v-text="innerContent" @input="sync"></div>
</template>
<script>
import Vue from 'vue'
export default Vue.extend({
name: 'Editor',
props: ['content'],
data () {
return {
innerContent: ''
}
},
methods: {
sync (e) {
this.$emit('update', e.target.innerHTML)
}
},
mounted() {
this.innerContent = this.content
}
})
</script>
<template>
<div class="hello">
<editor :content="content" @update="sync"></editor>
<div v-html="content"></div>
</div>
</template>
<script>
import Vue from 'vue'
import Editor from './Editor'
export default Vue.extend({
name: 'HelloWorld',
components: {
Editor
},
data () {
return {
content: 'sample text'
}
},
methods: {
sync (content) {
this.content = content
}
}
})
</script>
雑なサンプルですが、こんな感じで contenteditable
で v-model
っぽいデータの同期ができます。