Vue Component Does Not Show Node Child Text When Using Render Function

I am trying to make an editable component with Vue 2. It should use the contenteditable attribute in any tag, replacing normal input. I want to give it placeholder functionality to show the value when the user has not provided to anyone, but I cannot get it to work.

I look at the current value of the component and set data.isEmpty to true when user content is missing. Then the component should show a placeholder value, but currently it doesn't show anything.

If I console.log result of the render method, it will show that the child placeholder node was created correctly, but for some reason it just wonโ€™t display in the final HTML text.

Here's JSFiddle: https://jsfiddle.net/dy27fa8t/

And the inline snippet for those who prefer:

 Vue.component('editable-content', { props: { initial: { type: String }, placeholder: { type: String, required: false } }, data() { return { value: this.initial, isEmpty: this.initial === '' } }, render: function(createElement) { const self = this return createElement( 'div', { attrs: { contenteditable: true }, on: { input: function(event) { self.value = event.target.innerHTML self.$emit('edited', event.target.value) } } }, this.isEmpty ? this.placeholder : this.value ) }, watch: { value(to, from) { this.isEmpty = to === '' } } }) new Vue({ el: '#app', components: [ 'editable-content' ] }) 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script> <div id="app"> <editable-content initial="Initial value" placeholder="Placeholder" /> </div> 
+5
source share
3 answers

In the end, I decided to use a mixed JS and CSS solution using the :empty class pseudo-class. The Vue workaround only seemed too cumbersome, so it seemed like a good compromise. I don't even feel the need to track value anymore.

It is worth noting that with single-file components I can use cloud CSS, so it is even better, because CSS is necessary for the functionality of the component core.

 Vue.component('editable-content', { props: { initial: { type: String }, placeholder: { type: String, required: false } }, data() { return { value: this.initial } }, render: function(createElement) { const self = this return createElement( 'div', { attrs: { contenteditable: true, 'data-placeholder': this.placeholder }, on: { input: function(event) { self.$emit('edited', event.target.value) } } }, this.value ) } }) new Vue({ el: '#app', components: [ 'editable-content' ] }) 
 [data-placeholder]:empty::after { content: attr(data-placeholder); } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script> <div id="app"> <editable-content initial="Initial value" placeholder="Placeholder" /> </div> 
+2
source

Obviously, contenteditable rendering doesn't work intuitively. Instead, set innerHTML directly with a placeholder when the content is empty. Then on keydown (before the input event), if the content is currently marked empty, remove the placeholder. In keyup (after an input event), if the div still does not have content, mark it blank again (this is the case, for example, the shift key does not clear the placeholder).

I allowed to make it v-model compatible and to style the placeholder.

 Vue.component('editable-content', { props: { value: { type: String }, placeholder: { type: String, required: false } }, data() { return { isEmpty: this.value === '' }; }, methods: { setEmpty() { this.$el.innerHTML = `<div contenteditable="false" class="placeholder">${this.placeholder}</div>`; this.isEmpty = true; }, clearEmpty() { this.$el.innerHTML = ''; this.isEmpty = false; } }, mounted() { if (this.$el.innerHTML === '') { this.setEmpty(); } }, watch: { value(newValue) { if (newValue === '') { this.setEmpty(); } } }, render: function(createElement) { return createElement( 'div', { attrs: { contenteditable: true }, on: { keydown: () => { if (this.isEmpty) { this.clearEmpty(); } }, input: (event) => { this.$emit('input', event.target.textContent); }, keyup: () => { if (this.$el.innerHTML === '') { this.setEmpty(); } } } }, this.value ) } }); new Vue({ el: '#app', data: { startingBlank: '', editedValue: 'initial value' }, components: [ 'editable-content' ] }) 
 .placeholder { color: rgba(0,0,0, 0.5); } 
 <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script> <div id="app"> <editable-content v-model="startingBlank" placeholder="Placeholder"></editable-content> <editable-content v-model="editedValue" placeholder="Placeholder"></editable-content> </div> 
+2
source

If you do not pass the initial prop to the component, it will be undefined. So you should check if there is undefined:

 data() { return { value: this.initial, isEmpty: typeof this.initial === 'undefined' } }, 
+1
source

Source: https://habr.com/ru/post/1267354/


All Articles