3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

vue.jsでcontenteditableなコンポーネントをsyncするためのテクニック

Posted at

入力した内容によって「可変するようなデザイン」のフォーム要素を作る際に非常に便利な contenteditable
inputtextareaでは表現しづらいデザインを簡単に実現できます。

しかし、vueで、子供がcontenteditableの場合、v-modelを使い親と双方向にsyncしようとすると、うまくいきません。何かしらのハックが必要です。

以下の記事が非常に参考になったのですが、「親/子両方で値を変更できる場合」にうまく行かず、もう少し拡張しました。

参考
https://qiita.com/zaru/items/baacf5eb490094aaa150
https://qiita.com/soumi/items/7d6d2e8c5616ccbdc665

動作イメージ

ezgif-4-e403a94d7a04.gif

contenteditable では v-modelが動かない

親子でsyncするシンプルな input を contenteditable を置き換える、というシチュエーションを想像すると、以下のようなコードを思い浮かべると思います。

parend.vue
<template>
  <div>
    <p>親にあるinput(v-model)</p>
    <input v-model="myValue">
    <p>Child.vue内の可変するcontenteditable</p>
    <Child :value.sync="myValue" />
  </div>
</template>

<script>
import Child from '@/components/Child.vue'
export default {
  components: { Child },
  data() {
    return {
      myValue: '初期値'
    }
  }
}
</script>

以下のように、contenteditablev-model を併用するコードでは動きません。

Child(NGケース).vue
<template>
  <div contenteditable="true" v-model="localValue"></div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    },
  },
  computed: {
    localValue: {
      get: function() {
        return this.value
      },
      set: function(newValue) {
        this.$emit('update:value', newValue)
      }
    }
  }
}
</script>

参考記事で紹介されているように、キャレットが左に飛んでしまう問題の他に、
親子で同期してくれない問題を回避するために工夫したものが以下。

Child(OKパターン).vue
<template>
  <div
    contenteditable="true"
    @input="update"
    @focus="focus"
    @blur="blur"
    v-text="valueText"
  ></div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ''
    },
  },
  data() {
    return {
      focusIn: false,
      valueText: ''
    }
  },
  computed: {
    localValue: {
      get: function() {
        return this.value
      },
      set: function(newValue) {
        this.$emit('update:value', newValue)
      }
    }
  },
  watch: {
    localValue(newVal) {
      // キャレットが動く問題を回避
      if (!this.focusIn) {
        this.valueText = newVal
      }
    }
  },
  created() {
    // watchが走らない為、初回のみここで代入
    this.valueText = this.value 
  },
  methods: {
    update(e) {
      this.localValue = e.target.innerText
    },
    focus() {
      this.focusIn = true
    },
    blur() {
      this.focusIn = false
    }
  }
}
</script>

よりよい方法あるかもしれないが、一端メモ

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?