Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create text node with custom render function in Vue.js

Tags:

vue.js

vuejs2

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 nodes, 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 and 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 using a custom render method.

<h4>
  <a url="/some-link">This item is linked</a>
</h4>
<h4>
  Plain text item
</h4>

Side-notes:

  • question was originally raised for VueJS v2.1 & v2.2-beta (February 2017)
  • focus was on proper semantic nodes (text node vs. element node)
like image 417
Oliver Hader Avatar asked Feb 23 '17 11:02

Oliver Hader


People also ask

How do I create a custom component on Vue?

Open the file in your code editor. Create the component's template section by adding <template></template> to the top of the file. Create a <script></script> section below your template section. Inside the <script> tags, add a default exported object export default {} , which is your component object.

For what purpose is the Render () function used in Vue?

A render function returns a virtual DOM node, commonly named VNode in the Vue ecosystem, which is an interface that allows Vue to write these objects in your browser DOM. They contain all the information necessary to work with Vue.

Can I use JSX in Vue?

js. Love it or hate it, JSX is a popular extension to JavaScript that allows XML tokens in your scripts. If you want to create templates in your script files and you find Vue's render() function to be difficult to work with, JSX may be just what you need.

Can you write HTML in Vue?

Vue uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component instance's data. All Vue templates are syntactically valid HTML that can be parsed by spec-compliant browsers and HTML parsers.


2 Answers

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.

like image 85
Bert Avatar answered Oct 25 '22 03:10

Bert


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.

like image 36
Andrew H Avatar answered Oct 25 '22 02:10

Andrew H