I have a form in an admin area where users can input text with shortcodes in them:
Heat oven at [temp c=200]
What I want is that temp
shortcode to be parsed and transformed into a Vue 2 component when shown in the front-end.
Here's a simplified Temperature.vue
component:
<template>
<span class="temperature">
{{ celsius }}°C
</span>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
props: ['celsius'],
name: 'temperature'
}
</script>
And here's a simplified Instructions.vue
component which will parse and render the text with 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 substitution is printed in the front-end. But the <temperature>
tag is rendered literally and not as a Vue component, which is somewhat expected.
Heat oven at <temperature :celsius="200"></temperature>
What I can't seem to wrap my head around is what step I should take for it to actually transform into the Temperature
component which I have defined. Is it even possible? Are there more orthodox ways of doing this which I am missing?
There is also the security concern of using v-html
to render the text. I do not necessarily want this to be there, but I'm guessing it's even more far-fetched to expect the Temperature
component to appear from an escaped string. I can always sanitise prior to insertion on database, but I'd still like to avoid v-html
if at all possible.
To make the component dynamic, we can bind it to a set property with the v-bind directive. Your component is now bound with the component property in the data. If you switch the component to Test2 , it will automatically mount the Test 2 component. Test it out on your browser.
STEP 01: First, Import the Child Component into the Parent Component inside script tag but above export default function declaration. STEP 02: Then, Register the Child Component inside the Parent Component by adding it to components object. STEP 03: Finally, Use the Child Component in the Parent Component Template.
Simply use v-bind like :ref="'element' + result.id" or ref="`element${result.id}`" .
In order to make your example work you'll need to use the render function of your Instructions component. In the render function you can create a new Vue Component, lets say 'intruction', to which you'll pass the string that resulted from shortcode parse to use as template. In that component declaration you'll append the Temperature component as a child and it will render the prop that you've passed. And that's it. The example follows:
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>
Here is my approach, not sure if it is what you are after. It introduces a intermediate component to render stuff dynamically. Also, I modified Instructions.vue a bit for my testing, you can modify to fit 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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With