V模型未监听输入的值更改(Vue.js)

时间:2019-07-18 09:15:21

标签: javascript html vuejs2

我有一个对象属性,该属性可以侦听用户输入或可以通过视图进行更改。 与下面的片段:

  • 如果我键入了一些内容,则输入的值将更新,widget.Title.Name也将更新。
  • 如果我单击“外部更新”按钮,则属性widget.Title.Name将被更新,但上面字段中的值不会被更新。

预期结果:widget.Title.Name更改时,需要同时更新可编辑文本的值。

我不明白为什么不进行更新,如果我在vue检查器中检查属性,则所有字段(widget.Title.NameValue)都将正确更新,但是html不会更新。

Vue.component('editable-text', {
		template: '#editable-text-template',
		props: {
			value: {
				type: String,
				default: '',
			},
			contenteditable: {
				type: Boolean,
				default: true,
			},
		},
		computed: {
			listeners() {
				return { ...this.$listeners, input: this.onInput };
			},
		},
		mounted() {

			this.$refs["editable-text"].innerText = this.value;
		},
		methods: {
			onInput(e) {
				this.$emit('input', e.target.innerText);
			}
		}
		});
    
    	var vm = new Vue({
		el: '#app',
		data: {
			widget: {
        Title: {
          Name: ''
        }
      }
		},
		async created() {
			this.widget.Title.Name = "toto"
		},
    methods: {
			externalChange: function () {
				this.widget.Title.Name = "changed title";
			},
    }
})
button{
  height:50px;
  width:100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <editable-text v-model="widget.Title.Name"></editable-text>
  <template>Name : {{widget.Title.Name}}</template>
  <br>
  <br>
  <button v-on:click="externalChange">External update</button>
</div>

<template id="editable-text-template">
	<p ref="editable-text" v-bind:contenteditable="contenteditable"
	   v-on="listeners">
	</p>
</template>

我搜索了很多有关类似问题的主题,但是它们有反应性问题,我认为我在输入方面存在特定问题。您知道发生了什么吗?我试图添加一个监听器来更改事件,但是它没有在widget.Title.Name更改时触发。

2 个答案:

答案 0 :(得分:0)

要解决此问题,您需要做3件事。

  1. 添加与道具名称相同的手表属性(此处为值)
  2. 从Lodash添加去抖动功能以限制请求数量
  3. 添加了一个功能,可以在用户键入内容时将光标(插入位置)恢复到合适的位置

对于第三点:在输入开始时,更改widget.Title.Name的值时,组件将重新渲染,并且插入标记的位置将重新初始化为0。因此,您需要在最后一个位置重新更新它,否则您将只从右向左书写。

我已经用我的最终解决方案更新了上面的代码段。 我希望这会对其他来这里的人有所帮助。

Vue.component('editable-text', {
		template: '#editable-text-template',
		props: {
			value: {
				type: String,
				default: '',
			},
			contenteditable: {
				type: Boolean,
				default: true,
			},
		},
    //Added watch value to watch external change <-> enter here by user input or when component or vue change the watched property
    watch: {
			value: function (newVal, oldVal) { // watch it
				// _.debounce is a function provided by lodash to limit how
				// often a particularly expensive operation can be run.
				// In this case, we want to limit how often we update the dom
				// we are waiting for the user finishing typing his text
				const debouncedFunction = _.debounce(() => {
					this.UpdateDOMValue();
				}, 1000); //here your declare your function
				debouncedFunction(); //here you call it
				//not you can also add a third argument to your debounced function to wait for user to finish typing, but I don't really now how it works and I didn't used it. 
			}
		},
		computed: {
			listeners() {
				return { ...this.$listeners, input: this.onInput };
			},
		},
		mounted() {

			this.$refs["editable-text"].innerText = this.value;
		},
		methods: {
			onInput(e) {
				this.$emit('input', e.target.innerText);
			},
      UpdateDOMValue: function () {
				// Get caret position
				if (window.getSelection().rangeCount == 0) {
					//this changed is made by our request and not by the user, we
					//don't have to move the cursor
					this.$refs["editable-text"].innerText = this.value;
				} else {
					let selection = window.getSelection();
					let index = selection.getRangeAt(0).startOffset;

					//with this line all the input will be remplaced, so the cursor of the input will go to the
					//beginning... and you will write right to left....
					this.$refs["editable-text"].innerText = this.value;
					
					//so we need this line to get back the cursor at the least position
					setCaretPosition(this.$refs["editable-text"], index);

				}
				
			}
		}
		});
    
var vm = new Vue({
		el: '#app',
		data: {
			widget: {
        Title: {
          Name: ''
        }
      }
		},
		async created() {
			this.widget.Title.Name = "toto"
		},
    methods: {
			externalChange: function () {
				this.widget.Title.Name = "changed title";
			},
    }
})


	/**
	 * Set caret position in a div (cursor position)
	 * Tested in contenteditable div
	 * @@param el :  js selector to your element
	 * @@param caretPos : index : exemple 5
	 */
	function setCaretPosition(el, caretPos) {
		var range = document.createRange();
		var sel = window.getSelection();
		if (caretPos > el.childNodes[0].length) {
			range.setStart(el.childNodes[0], el.childNodes[0].length);
		}
		else
		{
			range.setStart(el.childNodes[0], caretPos);
		}		
		range.collapse(true);
		sel.removeAllRanges();
	}
button{
  height:50px;
  width:100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <editable-text v-model="widget.Title.Name"></editable-text>
  <template>Name : {{widget.Title.Name}}</template>
  <br>
  <br>
  <button v-on:click="externalChange">External update</button>
</div>

<template id="editable-text-template">
	<p ref="editable-text" v-bind:contenteditable="contenteditable"
	   v-on="listeners">
	</p>
</template>

答案 1 :(得分:0)

您可以使用$ root。$ children [0]

Vue.component('editable-text', {
    template: '#editable-text-template',
    props: {
        value: {
            type: String,
            default: '',
        },
        contenteditable: {
            type: Boolean,
            default: true,
        },
    },
    computed: {
        listeners() {
            return {...this.$listeners, input: this.onInput
            };
        },
    },
    mounted() {
        this.$refs["editable-text"].innerText = this.value;
    },
    methods: {
        onInput(e) {
            this.$emit('input', e.target.innerText);
        }
    }
});

var vm = new Vue({
    el: '#app',
    data: {
        widget: {
            Title: {
                Name: ''
            }
        }
    },
    async created() {
        this.widget.Title.Name = "toto"
    },
    methods: {
        externalChange: function(e) {
            this.widget.Title.Name = "changed title";
            this.$root.$children[0].$refs["editable-text"].innerText = "changed title";
        },
    }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="app">
    <editable-text v-model="widget.Title.Name"></editable-text>
    <template>Name : {{widget.Title.Name}}</template>
    <br>
    <br>
    <button v-on:click="externalChange">External update</button>
</div>

<template id="editable-text-template">
    <p ref="editable-text" v-bind:contenteditable="contenteditable" v-on="listeners">
    </p>
</template>