来自父级的Vue观察值反转

时间:2018-04-14 22:07:52

标签: vue.js vuejs2

我有一个自定义组件,允许用户输入文本并将其发送到后端,在那里我进行一些计算并使用html将新文本吐出来。

我的问题是当用户键入此textarea时,它会反转所有文本并将游标保留在textarea的开头。所以现在' foo bar'因为我加入了watch,所以只发生了这种情况。我可以删除观察者,但是当我将foo设置为等于来自父级的内容时,我需要它(或者需要另一种方式)将我的更新应用到textarea,通过foo变量。

console.log(v)写出反向文本。  知道怎么改变这个吗?

自定义组件:

    <template>
  <div contenteditable="true" @input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
  name: 'htmlTextArea',
  props:['value'],
  mounted: function () {
    this.$el.innerHTML = this.value;
  },
  watch: {
    value(v) {
      this.$el.innerHTML =  v; //v is the reverse text. 
    }
  },
  methods: {
    updateHTML: function(e) {
      this.$emit('input', e.target.innerHTML);

    }
  }
}
</script>

使用自定义组件的父级:

    <htmlTextArea id="textarea" v-model="foo"></htmlTextArea>

...
<script>
...
methods: {
        triggerOnClick() {
            this.foo = 'something';//Without the watcher, when I change this.foo to something the actual textarea does not display the new data that I assigned to foo. But in Vue dev tools I can see the new change. 
        }

更新:

&#13;
&#13;
Vue.component('html-textarea',{
  template:'<div contenteditable="true" @input="updateHTML"></div>',
  props:['value'],
  mounted: function () {
    this.$el.innerHTML = this.value;
  },
  watch: {
    value(v) {
      this.$el.innerHTML =  v;
    }
  },
  methods: {
    updateHTML: function(e) {
      this.$emit('input', e.target.innerHTML);

    }
  }
});

new Vue({
  el: '#app',
  data () {
        return {
            foo: '',
        }
    }
});
&#13;
<script src="https://unpkg.com/vue"></script>

<div id="app">Type here:
 <html-textarea spellcheck="false" id="textarea" v-model="foo">        </html-textarea>
</div>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

问题在于,当您设置innerHTML元素的contenteditable时,您将失去选择(光标位置)。

因此,在设置时应执行以下步骤:

  • 保存当前光标位置;
  • 设置innerHTML;
  • 恢复光标位置。

保存和恢复是棘手的部分。幸运的是,我得到了这两个方便的功能,可以完成最新IE和更新版本的工作。见下文。

&#13;
&#13;
function saveSelection(containerEl) {
  var range = window.getSelection().getRangeAt(0);
  var preSelectionRange = range.cloneRange();
  preSelectionRange.selectNodeContents(containerEl);
  preSelectionRange.setEnd(range.startContainer, range.startOffset);
  var start = preSelectionRange.toString().length;

  return {
    start: start,
    end: start + range.toString().length
  }
}

function restoreSelection(containerEl, savedSel) {
  var charIndex = 0, range = document.createRange();
  range.setStart(containerEl, 0);
  range.collapse(true);
  var nodeStack = [containerEl],
    node, foundStart = false,
    stop = false;

  while (!stop && (node = nodeStack.pop())) {
    if (node.nodeType == 3) {
      var nextCharIndex = charIndex + node.length;
      if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
        range.setStart(node, savedSel.start - charIndex);
        foundStart = true;
      }
      if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
        range.setEnd(node, savedSel.end - charIndex);
        stop = true;
      }
      charIndex = nextCharIndex;
    } else {
      var i = node.childNodes.length;
      while (i--) {
        nodeStack.push(node.childNodes[i]);
      }
    }
  }

  var sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
}


Vue.component('htmltextarea', {
  template: '#hta',
  name: 'htmlTextArea',
  props:['value'],
  mounted: function () {
    this.$el.innerHTML = this.value;
  },
  watch: {
    value(v) {
      if (v === 'yes') {
        let selection = saveSelection(this.$el);
        this.$el.innerHTML = 'no!';
      	this.$emit('input', 'no!');
        restoreSelection(this.$el, selection);
      }
    }
  },
  methods: {
    updateHTML: function(e) {
      this.$emit('input', e.target.innerHTML);
    }
  }
});

new Vue({
  el: '#app',
  data: {
    foo: 'Clear this and type "yes" (without the quotes). It should become "no!".'
  }
})
&#13;
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <htmltextarea id="textarea" v-model="foo"></htmltextarea>
  <hr>
  Result: <pre>{{ foo }}</pre>
</div>
<template id="hta">
    <div contenteditable="true" @input="updateHTML" class="textareaRoot"></div>
</template>
&#13;
&#13;
&#13;

在您的应用中,我建议您将它们放在专用的.js文件中,以便更好地进行组织。