16

I would like to create a vue element from a string from my database.

In this case, it should be a message with a smiley emoji. I actually save it like: Some text with Emoji: :santa::skin-tone-3:, and replace all valid string between '::' with the <Emoji emoji=':santa::skin-tone-3:' :size='16' />

<template>
  <span class=message v-html=convertedMessage></div>
</template>

<script>
  import { Emoji } from 'emoji-mart-vue'

  export default {
    components: {
      Emoji
    },
    computed:{
      convertedMessage(){
        return "Some text with Emoji: "+"<Emoji emoji=':santa::skin-tone-3:' :size='16' />"
      }
    }
  }
</script>

But instead of the rendered element which should be something like:

<span data-v-7f853594="" style="display: inline-block; width: 32px; height: 32px; background-image: url(&quot;https://unpkg.com/emoji-datasource-apple@4.0.4/img/apple/sheets/64.png&quot;); background-size: 5200%; background-position: 15.6863% 41.1765%;"></span>

I only get:

<emoji emoji=":santa::skin-tone-3:" :size="16"></emoji>

What is the best possibility to render this Element like intended?

db2
  • 195
  • 2
  • 13
Jakob Graf
  • 376
  • 1
  • 3
  • 14
  • I can make a library recommendation: https://github.com/alexjoverm/v-runtime-template is a nice abstraction. – danronmoon Aug 04 '19 at 02:48
  • Does this answer your question? [How to use components in v-html](https://stackoverflow.com/questions/37133282/how-to-use-components-in-v-html) – Adam Jun 18 '20 at 11:39

4 Answers4

9

Here are some much easier ways to do what you generally want. If you give more specifics, your right direction may be a strategy pattern before one of these solutions, but one of these solutions is probably what you want:

1) Vue lets you dynamically define components right out of the box, so this single line:

<component v-for="(component, index) in components" :key="'component'+index" :is="component.name" v-bind="component.props" />

...would draw a bunch of components in an array of objects like this (for example): {name: 'myComponentName', props: {foo: 1, bar: 'baz'}}.

2) Vue lets you inject HTML into components by simply adding v-html="variable"

For example, here is a component that creates dynamic SVG icons, where the contents of the SVG is dynamically injected from JavaScript variables...

<template>
  <svg xmlns="http://www.w3.org/2000/svg"
    :width="width"
    :height="height"
    viewBox="0 0 18 18"
    :aria-labelledby="name"
    role="presentation"
  >
    <title :id="name" lang="en">{{name}} icon</title>
    <g :fill="color" v-html="path">
    </g>
  </svg>
</template>

<script>
import icons from '../common/icons'
export default {
  props: {
    name: {
      type: String,
      default: 'box'
    },
    width: {
      type: [Number, String],
      default: 18
    },
    height: {
      type: [Number, String],
      default: 18
    },
    color: {
      type: String,
      default: 'currentColor'
    }
  },
  data () {
    return {
      path: icons[this.name]
    }
  },
  created () {
    console.log(icons)
  }
}
</script>

<style scoped>
  svg {
    display: inline-block;
    vertical-align: baseline;
    margin-bottom: -2px;
  }
</style>

3) Vue lets you dynamically define your component template through this.$options.template:

export default {
  props: ['name', 'props'],
  template:  '',
  created(){
    this.$options.template = `<component :is="name" ${props.join(' ')} ></component>`
  },
}

4) Vue lets you define a render function, so proxy components or other advanced shenanigans are trivial:

Vue.component('component-proxy', {
  props: {
    name: {
      type: String,
      required: true
    },
    props: {
      type: Object,
      default: () => {}
    }
  },
  render(h) {
    // Note the h function can render anything, like h('div') works too.
    // the JS object that follows can contain anything like on, class, or more elements
    return h(this.name, {
      attrs: this.props
    });
  }
});

A smart genius wrote a jsbin for this here: http://jsbin.com/fifatod/5/edit?html,js,output

5) Vue allows you to create components with Vue.extend or even passing in raw JavaScript objects into a page or apps components section, like this, which creates a component named "foo" from a simple string for the template and an array for props, you could also extend the data, created, on, etc. the same way using the JS object alone:

new Vue({
  el: '#app',
  data: {
    foo: 'bar',
    props: {a: 'a', b: 'b'}
  },
  components: {
    foo: {
      template: '<p>{{ a }} {{ b }}</p>',
      props: ['a', 'b']
    }
  }
})
Nick Steele
  • 7,419
  • 4
  • 36
  • 33
  • 1
    Nick, can you give me some advice about inserting vue-components into content editable and what string should I store to database to recreate user input to wysiwyg vue-editor? Here is my question https://stackoverflow.com/questions/59849554/how-to-insert-vue-component-into-contenteditable-div – webprogrammer Jan 23 '20 at 10:44
  • 1
    Sure thing! :) Answered and gave you an example codepen to play with. (https://codepen.io/njsteele/pen/wvBNYJY). I also wrote a super simple var template parser, which will let you inject live vue components that update inside the editor, and do other cool things (https://github.com/onexdata/nano-var-template) Opting to go the var template route will let you use shortcodes instead of HTML output (i.e. if you store the editor content in a database, it will both be smaller, and dynamic / update as you update your components). Let me know if you need any more help. – Nick Steele Jan 23 '20 at 20:40
  • Oh, that's great!!! I will check it tomorrow early in the morning. Thank you! – webprogrammer Jan 23 '20 at 20:52
  • hey guys, possible to get point 4) working for vue3? I tried using the same techniques but didnt work. http://jsbin.com/fifatod/5/edit?html,js,output – mingsterism Jul 24 '21 at 14:11
4

What i figured out now:

convertedMessage(){
    let el = Vue.compile("<Emoji emoji=':santa::skin-tone-3:' :size='16' />")
    el = new Vue({
        components: {
            Emoji
        },
        render: el.render,
        staticRenderFns: el.staticRenderFns
    }).$mount()
    return "Some text with Emoji: "+el.$el.innerHTML
}

Maybe there is still a better solution to handle this?

Jakob Graf
  • 376
  • 1
  • 3
  • 14
  • You don't want to do this for two main reasons: First, Vue.compile adds to your build size, as it's not included by default since it's considered a build-time function. Second, compiling at runtime is "absolutely wrong for production" according to the VueJS docs. The reasoning is compiling a vue component at runtime is the slowest possible way you can render a component in Vue. – Nick Steele Nov 07 '19 at 15:29
  • 1
    @NickSteele So how would i do such a thing if i can only return a string and not html directly? I came across this as i am trying to do something similar. – Nico Bleiler Nov 12 '19 at 15:05
  • @NicoBleiler Do you mean "I can only return a string" as in from a function? I'm not sure I follow your question, but my answer here as to the ways that Vue lets you render components covers pretty much every way you can build dynamic vue components: https://stackoverflow.com/a/53752539/3196360 – Nick Steele Nov 12 '19 at 16:55
  • @jacob-graf, have you found the better solution? – webprogrammer Jan 23 '20 at 10:39
3

Here's how I went about when I needed to do something similar.

I rendered the component, say, <my-component> normally, but since I only needed rendered HTML, I wrapped it inside a <div class="hidden"> like so:

<div class="hidden">
   <my-component />
</div>

With CSS:

.hidden {
  display: none;
}

This way, I can refer to the element through $refs or you could get the element from the DOM using document.querySelector() while keeping it invisible to the end users.

So in the above example, to get the rendered HTML, You'd only need to do this:

let el = document.querySelector('.hidden');
let renderedHTMLString = el.children[0].outerHTML;

This way, you get the rendered HTML, without any overhead costs that's associated with Vue.compile or any other plugin. Render it normally. Hide it. Access it's outerHTML.

Amit Joki
  • 58,320
  • 7
  • 77
  • 95
  • 1
    Sometimes you can't load it beforehand. For example, if you receive the HTML through ajax. – Adam Jun 18 '20 at 11:34
2

v-html only render plain HTML, see https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML

In your case you should probably take a look at render functions and JSX. I'm not an expert but it seems that some people are acheiving what you want with the dangerouslySetInnerHTML JSX function. Take a look at this thread : How do I convert a string to jsx?

I know sometimes we have no choice but if you can I think the best solution could be to avoid generating the template from the backend as it breaks separation of concern (and also, probably, security issues).

tony19
  • 125,647
  • 18
  • 229
  • 307
budgw
  • 710
  • 1
  • 5
  • 13