4

I'm using Vue.js and have the following code. When I type in div and this.content is updated, the caret is always reset to the beginning.

<template>
<div> 
  <div contenteditable="true"
    v-html="content"
    @input="onContentChange($event)">
  </div>
</div>
</template>
<script>
export default {
  props: ['content'],
  methods: {
    onContentChange: function(e) {
      this.content = e.target.innerHTML;
    },
  }, 
}
</script>
<style>
</style>

How can I preserve the caret's position and update the content?

I've seen some other similar posts, but the solutions there either are not for Vue.js, or don't work in my case, or I might have failed to apply them correctly.

Schostac
  • 71
  • 2
  • 6
  • Why are you replacing `content`? It already contains the edit and has the caret at the last position. If you replace the innerHTML it will effectively build new elements inside your View and, obviously, it will lose the caret position. You basically have two cases and you need to differentiate them: when the edit comes from user (in which case you don't want to replace `content`) and when the edit comes from an external source. If you want the caret to be retained in the second case, look [here](https://stackoverflow.com/questions/3972014/get-caret-position-in-contenteditable-div). – tao Jul 21 '19 at 12:36
  • 1. No, this.content doesn't already contain the edit. When I add console.log(this.content), it shows the old text. Note v-html and that it is not input or text area. 2. That post is about getting the position, but I have problem rather with setting the position. – Schostac Jul 21 '19 at 13:03
  • 1
    What I'm saying is you don't have to assign the edited value (`e.target.innerHTML`) to `content`. Therefore you have the change (`e.target.innerHTML`) and the caret position. Don't expect `content` to update to your latest edit (it's not `v-model`, it's `v-html`). It only replaces the innerHTML with latest value of `content`, when `content` changes. If you need `v-model` behavior, use ` – tao Jul 21 '19 at 13:25
  • Basically, in the end I want to wrap selected part of text in some tags without losing caret position. But if I change either this.content or innerHTML, caret position is reset. So I need to restore the caret position. – Schostac Jul 21 '19 at 13:53

2 Answers2

0

I've tested a few scenarios and I think what you actually need is plainly the Create a reusable editable component in this post.

However, if you want to have everything in one component, in chrome the following code works:

<template>
  <div
    ref="editable"
    contenteditable
    @input="onInput"
  >
  </div>
</template>

<script>
export default {
  data () {
    return {
      content: 'hello world'
    }
  },
  mounted () {
    this.$refs.editable.innerText = this.content
  },
  methods: {
    onInput (e) {
      this.content = e.target.innerText
    }
  }
}
</script>

Note that the Vue plugin in Chrome doesn't seem to update correctly the value of content in this scenario, therefore you have to click on refresh on the top right of the vue plugin.

Damien Baldy
  • 456
  • 2
  • 7
0

First we preserve current click on the contenteditable, then change HTML content, and set new selection.

const range = document.getSelection().getRangeAt(0)
const pos = range.endOffset

this.$el.innerHTML = this.content

const newRange = document.createRange()
const selection = window.getSelection()
const node = this.$el.childNodes[0]
newRange.setStart(node, node && pos > node.length ? 0 : pos)
newRange.collapse(true)
selection.removeAllRanges()
selection.addRange(newRange)
Laurensius Adi
  • 252
  • 3
  • 19