Dynamic html elements in Vue.js

How can I dynamically add elements to content? Example below:

<template>
    {{{ message | hashTags }}}
</template>

<script>
    export default {
        ...

        filters: {
            hashTags: function(value) {
                // Replace hash tags with links
                return value.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>')
            }
        }
    }
</script>

The problem is that if I click the link, there will be no action. Vue does not see new elements.

+8
source share
6 answers

Update: Based on this answer, you can create a similar dynamic template component in Vue 2. You can customize the component specification in a computedsection and link it using :is

var v = new Vue({
  el: '#vue',
  data: {
    message: 'hi #linky'
  },
  computed: {
    dynamicComponent: function() {
      return {
        template: '<div>${this.hashTags(this.message)}</div>',
        methods: {
          someAction() {
            console.log("Action!");
          }
        }
      }
    }
  },
  methods: {
    hashTags: function(value) {
      // Replace hash tags with links
      return value.replace(/#(\S*)/g, '<a v-on:click="someAction">#$1</a>')
    }
  }
});

setTimeout(() => {
  v.message = 'another #thing';
}, 2000);
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="vue">
  <component :is="dynamicComponent" />
</div>
Run codeHide result

Vue HTML. -, , , . Vue ; . , , , .

/<partial> <partial> HTML, , :

  • HTML
  • HTML
  • ,

- , , , , HTML , .

message.

message , , , , .

var v = new Vue({
  el: 'body',
  data: {
    message: 'hi #linky'
  },
  computed: {
    partialName: function() {
      Vue.partial(this.message, this.hashTags(this.message));
      return this.message;
    }
  },
  methods: {
    someAction: function() {
      console.log('Action!');
    },
    hashTags: function(value) {
      // Replace hash tags with links
      return value.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>')
    }
  }
});

setTimeout(() => {
  v.$set('message', 'another #thing');
}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<partial :name="partialName"></partial>
Hide result
+6

$compile, , , . , $compile, .

Vue.directive('dynamic', function(newValue) {
    this.el.innerHTML = newValue;
    this.vm.$compile(this.el);
});

var v = new Vue({
  el: 'body',
  data: {
    message: 'hi #linky'
  },
  computed: {
    messageAsHtml: function() {
      return this.message.replace(/#(\S*)/g, '<a v-on:click="someAction()">#$1</a>');
    }
  },
  methods: {
    someAction: function() {
      console.log('Action!');
    }
  }
});

setTimeout(() => {
  v.$set('message', 'another #thing');
}, 2000);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<div v-dynamic="messageAsHtml"></div>
Hide result
+9

partial VueJS 2 (https://vuejs.org/v2/guide/migration.html#Vue-partial-removed)

, DOM.

<process-text>Hi #hashtag !</process-text>
Vue.component('process-text', {
    render: function (createElement) {
        var hashtagRegex = /(^|\W)(#[a-z\d][\w-]*)/ig
        var text = this.$slots.default[0].text
        var list = text.split(hashtagRegex)
        var children = []
        for (var i = 0; i < list.length; i++) {
            var element = list[i]
            if (element.match(hashtagRegex)) {
                children.push(createElement('a', {
                attrs: {
                    href: 'https://www.google.fr/search?q=' + element,
                    target: "_blank"
                    },
                domProps: {
                    innerHTML: element
                    }
                }))
            } else {
                children.push(element)
            }
        }
    }
    return createElement('p', {}, children)  // VueJS expects root element
})
0

The best solution I found works fine with custom html, it looks like this: it seems like you create a new component every time the html property changes. Nobody actually did this, we just use a computed property to create a new component.

Here's what it looks like:

new Vue({
  el: "#root",
  data: {
      value: '',
      name: 'root',
      htmlData: '<div><input @input="onInputProxy($event)" ' +
                            'v-model="value" ' + 
                            'v-for="i in 3" ' + 
                            ':ref="`customInput${i}`"></div>'
  },
  computed: {
    // our component is computed property which returns the dict
    htmlDataComponent () {
      return {
        template: this.htmlData, // we use htmlData as template text

        data() {
          return {
            name: 'component',
            value: ''
          }
        },
        created () {
          // value of "this" is formComponent
          console.log(this.name + ' created');
        },
        methods: {
          // proxy components method to parent method,
          // actually you done have to
          onInputProxy: this.onInput
        }
      }
    }
  },
  methods: {
    onInput ($event) {
      // while $event is proxied from dynamic formComponent
      // value of "this" is parent component
      console.log(this.name + ' onInput');

      // use refs to refer to real components value
      console.log(this.$refs.htmlDataComponent.value);
      console.log(this.$refs.htmlDataComponent.$refs.customInput1);
      console.log(this.$refs.htmlDataComponent.$refs.customInput2);
      console.log(this.$refs.htmlDataComponent.$refs.customInput3);
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js">
</script>

<div id="root">
  <component ref="htmlDataComponent" 
             v-if="htmlData"
             :is="htmlDataComponent"></component>
</div>
Run codeHide result

I have not tested it for memory efficiency, but it looks like everything is working fine.

0
source

A modified version of @RoyJ's answer, works in Vue.js v2.6.10

new Vue({
    ...,
    computed: {
        inner_html() {
            return ...; // any raw html
        },
    },
    directives: {
        dynamic: {
            bind(el, binding) {
                el.innerHTML = binding.value;
            },
            update(el, binding) {
                el.innerHTML = binding.value;
            },
        },
    },
    template: '<div v-dynamic='inner_html'></div>',
});
0
source

In Vue.js 2, this is simpler:

new Vue({
    ...,
    computed: {
        inner_html() {
            return ...; // any raw html
        },
    },
    template: '<div v-html='inner_html'></div>',
});
0
source

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


All Articles