javascript - Vue component not showing child text node when using render function - Stack Overflow

I'm trying to do an editable ponent with Vue 2. It supposed to use the contenteditable attribute i

I'm trying to do an editable ponent with Vue 2. It supposed to use the contenteditable attribute in any tag, replacing a normal input. I want to give it a placeholder functionality in order to show a value when none is provided by the user, but I can't seem to get it working.

I'm watching the current value of the ponent and setting data.isEmpty to true when no user content is present. The ponent should then show the placeholder value, but currently it shows nothing.

If I console.log the result of the render method, it will show the placeholder child node was instantiated correctly, but for some reason it just won't show on the final HTML.

Here's a JSFiddle: /

And an embedded snippet for those who prefer it:

Vueponent('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',
  ponents: [
    'editable-content'
  ]
})
<script src=".3.0/vue.min.js"></script>

<div id="app">
  <editable-content initial="Initial value" placeholder="Placeholder" />
</div>

I'm trying to do an editable ponent with Vue 2. It supposed to use the contenteditable attribute in any tag, replacing a normal input. I want to give it a placeholder functionality in order to show a value when none is provided by the user, but I can't seem to get it working.

I'm watching the current value of the ponent and setting data.isEmpty to true when no user content is present. The ponent should then show the placeholder value, but currently it shows nothing.

If I console.log the result of the render method, it will show the placeholder child node was instantiated correctly, but for some reason it just won't show on the final HTML.

Here's a JSFiddle: https://jsfiddle/dy27fa8t/

And an embedded snippet for those who prefer it:

Vue.ponent('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',
  ponents: [
    'editable-content'
  ]
})
<script src="https://cdnjs.cloudflare./ajax/libs/vue/2.3.0/vue.min.js"></script>

<div id="app">
  <editable-content initial="Initial value" placeholder="Placeholder" />
</div>

Share Improve this question asked May 1, 2017 at 14:43 Tomas ButelerTomas Buteler 4,1374 gold badges32 silver badges43 bronze badges 4
  • When you say, "when none is provided by the user" do you mean when there is no initial attribute bound on the ponent? – Bert Commented May 1, 2017 at 14:53
  • Well, that too. But I'm mostly referring to the user going to edit the element and deleting all characters. I'm basically trying to reproduce the placeholder on an HTML input, so when no value is set the placeholder shows up. – Tomas Buteler Commented May 1, 2017 at 14:58
  • 1 It has something to do with it re-using the DOM element and the DOM element having focus. What I mean is, if you change the value without it having focus (like with setTimeout) it sets the placeholder value. Likewise, if you add a unique key on each render, it will set the placeholder, but you lose focus on the DOM element. – Bert Commented May 1, 2017 at 15:25
  • I've tested this and you're right. Bummer. Sure hope there's a workaround, because losing focus on every edit sort of defeats the purpose of having this ponent... – Tomas Buteler Commented May 1, 2017 at 15:33
Add a ment  | 

3 Answers 3

Reset to default 2

Apparently rendering a contenteditable doesn't work in the intuitive way. Instead, set the innerHTML directly with the placeholder when the content is empty. Then on keydown (before the input event), if the content is currently marked empty, remove the placeholder. On keyup (after the input event), if the div still has no content, mark it empty again (this is so things like shift key don't clear the placeholder).

I took the liberty of making it v-model patible and styling the placeholder.

Vue.ponent('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'
  },
  ponents: [
    'editable-content'
  ]
})
.placeholder {
  color: rgba(0,0,0, 0.5);
}
<script src="//cdnjs.cloudflare./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>

In the end I settled for a mixed JS and CSS solution using the :empty pseudo-class. A Vue-only workaround just seemed too unwieldy, so this felt like a healthy promise. I don't even feel the need to keep track of the value anymore.

Worth noting that with single-file ponents I can use scoped CSS so it's even better as the CSS is essential to the ponents core functionality.

Vue.ponent('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',
  ponents: [
    'editable-content'
  ]
})
[data-placeholder]:empty::after {
  content: attr(data-placeholder);
}
<script src="https://cdnjs.cloudflare./ajax/libs/vue/2.3.0/vue.min.js"></script>

<div id="app">
  <editable-content initial="Initial value" placeholder="Placeholder" />
</div>

If you don't pass the initial prop into the ponent it will be undefined. So the check you should make is to see if is undefined:

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

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745349583a4623749.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信