I'm using a my-link
component to wrap an anchor tag on demand around various items. For that purpose a custom render
method is used - however the createElement
method can only create HTML node, creating plain text nodes does not seem to be possible.
Current scenario
Usage of my-link
component
<template v-for="item in items">
<h4>
<my-link :url="item.url">{{ item.text }}</my-link>
</h4>
</template>
Implementation of my-link
component as Link.vue
<script>
export default {
name: 'my-link',
props: { url: String },
render(createElement) {
if (this.url) {
return createElement(
'a', {
attrs: { href: this.url }
}, this.$slots.default
);
}
return createElement(
'span',
this.$slots.default
);
}
};
</script>
Resulting HTML
<h4>
<a url="/some-link">This item is linked</a>
</h4>
<h4>
<span>Plain text item</span>
</h4>
Desired scenario
The span
tag in this particular scenario is superfluous an could be avoided - however, it's not clear to me how and whether at all this is possible with Vue.js. In general I'd like to know how to create plain text nodes in custom render
methods.
<h4>
<a url="/some-link">This item is linked</a>
</h4>
<h4>
Plain text item
</h4>
Vue exposes an internal method on it's prototype called _v that creates a plain text node. You can return the result of calling this method from a render function to render a plain text string:
render(h){
return this._v("my string value");
}
Exposing it in this way, prefixed with an underscore, likely indicates it's intended as a private API method, so use with care.
If you use a functional component, "this" is also not available. In this case, you should call context._v(), for example:
functional: true,
render(h, context){
return context._v("my string value")
}
This, combined with extracting the text from the slot (as in your comment, using the helpful getChildrenTextContent) will produce the desired result.
You can actually get around having to use the _v method mentioned in answers above (and potentially avoid using an internal Vue method that might get renamed later) by changing your implementation of the the MyLink component to be a functional component. Functional components do not require a root element, so it would get around having to put a span around the non-link element.
MyLink could be defined as follows:
const MyLink = {
functional: true,
name: 'my-link',
props: { url: String },
render(createElement, context) {
let { url } = context.props
let slots = context.slots()
if (url) {
return createElement(
'a', {
attrs: { href: url }
}, slots.default
);
}
else {
return slots.default
}
}
};
Then to use it, you could do something like this in a different component:
<div>
<h4 v-for="item in items">
<my-link :url="item.url">{{ item.text }}</my-link>
</h4>
</div>
See: https://codepen.io/hunterae/pen/MZrVEK?editors=1010
Also, as a side-note, it appears your original code snippets are naming the file as Link.vue. Since you are defining your own render function instead of using Vue's templating system, you could theoretically rename the file to Link.js and remove the beginning and closing script tags and just have a completely JS component file. However, if you component includes custom systems (which your snippet did not), this approach will not work. Hope this helps.
You need a root element for a component. In your case, maybe you can use 'h4' as the root element since I see you include that anyway in the v-for loop. Once you have done that, you can then create a text node like this.
return createElement(
'h3',
{},
['Plain text item']
);
According to Vue documentation, the third argument of createElement could be string or array, if it is a string it will be converted to text node.
https://vuejs.org/v2/guide/render-function.html#createElement-Arguments
In Vue 2.5.x, you can use this to render text nodes:
render(createElement) {
return createElement(() => {
return document.createTextNode('Text');
});
}