I have a textarea
component that include html tag
and I want to get html
in edit mode in this component. I use Laravel
to
components are treated as static by the Vue renderer, thus after they are put into the DOM, they don't change at all (so that's why if you inspect the DOM you'll see
inside your ).
But even it if they did change, that wouldn't help much. Just because HTML elements inside s don't become their value. You have to set the
value
property of the TextArea element to make it work.
Anyway, don't despair. It is doable, all you need to overcome the issues above is to bring a small helper component into play.
There are many possible ways to achieve this, two shown below. They differ basically in how you would want your original component's template to be.
into
componentYour component's template would now become:
As you can see, nothing but replacing with
changed. This is enough to overcome the static treatment Vue gives to . The full implementation of
is in the demo below.
but get
's HTML via
componentThe solution is to create a helper component (named vnode-to-html
below) that would convert your slot's VNodes into HTML strings. You could then set such HTML strings as the value
of your . Your component's template would now become:
The usage of the my-component
stays the same:
hello world
Vue.component('my-component', {
props: ["content", "name", "id"],
template: `
`,
data() { return {valueForMyTextArea: '', myContent: null} }
});
Vue.component('textarea-slot', {
props: ["value", "name", "id"],
render: function(createElement) {
return createElement("textarea",
{attrs: {id: this.$props.id, name: this.$props.name}, on: {...this.$listeners, input: (e) => this.$emit('input', e.target.value)}, domProps: {"value": this.$props.value}},
[createElement("template", {ref: "slotHtmlRef"}, this.$slots.default)]
);
},
data() { return {defaultSlotHtml: null} },
mounted() {
this.$emit('input', [...this.$refs.slotHtmlRef.childNodes].map(n => n.outerHTML).join('\n'))
}
});
Vue.component('vnode-to-html', {
props: ['vnode'],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit('html', [...this.$el.childNodes].map(n => n.outerHTML).join('\n'));
}
});
new Vue({
el: '#app'
})
hell
o world1
hello world2
Breakdown:
s into VNodes and makes them available in the this.$slots.SLOTNAME
property. The default slot, naturally, goes in this.$slots.default
.
(as VNodes in this.$slots.default
). The challenge now becomes how to convert those VNodes to HTML String? This is a complicated, still open, issue, which may get a different solution in the future, but, even if it ever does, it will most likely take a while.template-slot
and vnode-to-html
) use Vue's render function to render the VNodes to the DOM, then picks up the rendered HTML.
tags.vnode-to-html
returns as an event that should be picked up by the parent (my-component
) which uses the passed value to set a data
property that will be set as :value
of the textarea
.textarea-slot
declares itself a
, to the parent doesn't have to. It is a cleaner solution, but requires more care because you have to specify which properties you want to pass down to the
created inside textarea-slot
.However possible, it is important to know that Vue, when parsing the declared into
s, will strip some formatting information, like whitespaces between top-level components. Similarly, it strips tags (because they are unsafe). These are caveats inherent to any solutions using
s (presented here or not). So be aware.
Typical rich text editors for Vue, work around this problem altogether by using v-model
(or value
) attributes to pass the code into the components.
Well known examples include:
They all have very good documentation in their websites (linked above), so it would be of little use for me to repeat them here, but just as an example, see how codemirror uses the value
prop to pass the code:
So that's how they do it. Of course, if
s - with its caveats - fit your use case, they can be used as well.