Dynamically inject Vue 2 component from shortcode

I have a form in the admin area where users can enter text with short codes in them:

Heat furnace at [temp c = 200]

I want temp shortcode to be processed and converted to a Vue 2 component when it shows up in the interface.

Here's a simplified simplified Temperature.vue component:

 <template> <span class="temperature"> {{ celsius }}&deg;C </span> </template> <script> import { mapGetters, mapActions } from 'vuex' export default { props: ['celsius'], name: 'temperature' } </script> 

And here's a simplified Instructions.vue component that will parse and display text using shortcodes:

 <template> <div v-html="parseShortcodes(text)"></div> </template> <script> import ShortcodeParser from 'meta-shortcodes' import Temperature from 'Temperature.vue' export default { data: () => { return { text: 'Heat oven at [temp c=200]', parser: ShortcodeParser() } }, components: [ Temperature ], methods: { parseShortcodes(text) { return this.parser.parse(text) } }, created() { this.parser.add('temp', function(options) { return '<temperature :celsius="'+options.c+'"></temperature>' }) } } </script> 

The parsing works fine, and the lookup is printed on the interface. But the <temperature> displayed literally, and not as a Vue component, which is partly expected.

Heat oven at <temperature: celsius = "200"> </ temperature>

What I seem to be unable to wrap up is what I must take to ensure that it actually transforms into the Temperature component that I defined. Is it possible? Are there even more orthodox ways to do this that I am missing?

There is also a security issue when using v-html to render text. I don’t necessarily want it to be there, but I assume it is even more far-fetched to expect the Temperature component to appear from the escaped string. I can always sanitize before entering the database, but I would still like to avoid v-html , if at all possible.

+5
source share
3 answers

To make your example work, you will need to use the render function of your Instructions component. In the rendering function, you can create a new Vue component, say, 'intruction', to which you will pass the string that came from the parsing shortcode to use as a template. In this component declaration, you add the temperature component as a child, and it will pass the skip you skipped. And so it is. Example:

Instructions.vue

 <script> import ShortcodeParser from 'meta-shortcodes' import Temperature from 'Temperature.vue' export default { data: () => { return { text: 'Heat oven at [temp c=200]', parser: ShortcodeParser() } }, methods: { parseShortcodes(text) { return this.parser.parse(text) } }, render (createElement) { const template = this.parseShortcodes(this.text) //returns something like this <div class="intruction">'Heat oven at <temperature :celsius="200"></temperature>'</div> var component = Vue.component('instruction', { template: template, components: { 'temperature': Temperature } }) return createElement( 'div', { class: { instructions: true } }, [ createElement(component) ] ) } } </script> 
+4
source

Here is my approach, not sure if this is what you need. It introduces an intermediate component to dynamically display the material. In addition, I changed Instructions.vue a little for my testing, you can change it according to your shortcode parser

Instructions.vue

 <template> <div> <input type="text" @input="parseInput" /> <DynamicComponent :type="parsedType" :props="parsedProps"></DynamicComponent> </div> </template> <script> import DynamicComponent from './DynamicComponent'; export default { components: { DynamicComponent }, data() { return { parsedType: '', parsedProps: '' }; }, methods: { parseInput(e) { // parse the input if (e.target.value === '[temp c=200]') { // change this to use your parser this.parsedType = 'Temperature'; this.parsedProps = { celsius: 200 }; } else { this.parsedType = ''; this.parsedProps = ''; } } } }; </script> 

DynamicComponent.vue

 <script> import Temperature from './Temperature'; // import all other dynamically rendered components export default { components: { // let it know what components are there Temperature }, props: ['type', 'props'], render(h) { return h(this.type, { props: this.props }, this.$slots.default); } }; </script> 
+2
source

There is currently no step where you compile the template, so it appears as text. I think you could do the following to replace the text with a component

  • Once you find that the temperature wraps it in an html element
  • Create a new Vue instance in JavaScript (and not as an html template in line)
  • Return a vue instance on top of this element using the el or $ mount option
  • Use interpolation instead of v-html to prevent XSS https://vuejs.org/v2/guide/syntax.html#Text
0
source

Source: https://habr.com/ru/post/1259674/


All Articles