Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically inject Vue 2 component from shortcode

Tags:

vue.js

vuejs2

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 }}&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 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.

like image 836
Tomas Buteler Avatar asked Nov 13 '16 10:11

Tomas Buteler


People also ask

How do I use dynamic components in Vue?

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.

How do I import components into Vue 2?

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.

How do I create a dynamic reference in Vue?

Simply use v-bind like :ref="'element' + result.id" or ref="`element${result.id}`" .


2 Answers

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>
like image 102
António Quadrado Avatar answered Oct 16 '22 10:10

António Quadrado


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>
like image 43
xiaofan2406 Avatar answered Oct 16 '22 11:10

xiaofan2406