最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Vue.js mount component after DOM tree mutation to add a vue component - Stack Overflow

programmeradmin8浏览0评论

I have a use case (below) where I need to mount (if thats the correct term) a Vue.js ponent template that was inserted into the DOM via jQuery, I can setup a Mutation Observer or react to certain events that are triggered when the mutation happens.

I am using Vue.js v2

Here is a simple example I put together to illustrate the point:

live jsFiddle /

The HTML below contains inlined-templates for two ponents

<script src="/[email protected]/dist/vue.min.js"></script>
<script src=".3.1.min.js"></script>
<div id="app">
  <!-- The use of inline-template is required for my solution to work -->
  <simple-counter inline-template>
    <button v-bind:style="style" v-on:click="add">clicks: {{ counter }}</button>
  </simple-counter>
  <simple-counter inline-template>
    <button v-on:click="counter += 1">{{ counter }}</button>
  </simple-counter>
</div>

<button id="mutate">Mutate</button>

The js:

// simple counter ponent
Vueponent('simple-counter', {
  data: function() {
    return {
      counter: 0,
      style: {
        color: 'red',
        width: '200px'
      }
    }
  },
  methods: {
    add: function() {
      this.counter = this.counter + 1;
      this.style.color = this.style.color == 'red' ? 'green' : 'red';
    }
  }
})

// create the Vue instance
var initV = () => new Vue({
  el: '#app'
});

// expose the instance for later use
window.v = initV();

// click handler that will add a new `simple-counter` template to the Vue.el scope
$('#mutate').click(function(){
    $('#app').append(`  <div is="simple-counter" inline-template>
    <button v-bind:style="style" v-on:click="add">click to add: <span class="inactive" v-bind:class="{ active: true }">{{ counter }}</span></button></div>`)
    // do something after the template is incerted
    window.v.$destroy()
    window.v = initV(); // does not work

})

As mentioned in the code, destroying the re-instantiating the Vue instance does not work, I understand why, the templates for the ponents are changed on first Vue instantiation to their final HTML, when you try and instantiate a second time, templates are not there, ponents are not mounted

I'd like to be able to find the newly added ponents after mutation and mount only those, is that possible? and how?

UPDATE: I was able to find a way to do it via instantiating a new Vue instance with el set to the specific mutated part of the DOM as opposed to the whole #app tree:

$('#mutate').click(function(){
    var appended = 
            $(`
       <div is="simple-counter" inline-template>
         <button v-bind:style="style" v-on:click="add">
           click to add: {{ counter }}
         </button>
       </div>`
      ).appendTo($('#app'));

    var newV = new Vue({el: appended[0]});
});

Seems to work, but also looks ugly and I am not sure what other implications this might have..

Use Case:

I am working on a way to write Vue.js ponents for a CMS called Adobe Experience Manager (AEM).

I write my ponents using inlined-template which gives me the advantage of SEO as well as server-side rendering using another templating language called HTL.

The way AEM authoring works is that, when a ponent is edited (via a dialog), that specific ponent is re-rendered on the server-side then injected back to the DOM to replace the old ponent, all done via Ajax and jQuery (no browser refresh).

Here is an example

AEM ponent template: <button>${properties.buttonTitle}</button>

Here is what an author might do:

  1. author visits the authoring page
  2. opens the button ponent dialog to edit
  3. changes the buttonTitle to "new button title"
  4. Saves

upon saving, an ajax is sent, the ponent HTML is re-rendered on the server and returned is the new HTML. That HTML now replaces the old HTML via jQuery (mutates the DOM)

This is fine for static ponents, but if this was a Vue.js ponent, how do I dynamically mount it while keeping other ponents mounted.

An easy solution to this is to refresh the page... but that is just bad experience... There has to be a better way.

I have a use case (below) where I need to mount (if thats the correct term) a Vue.js ponent template that was inserted into the DOM via jQuery, I can setup a Mutation Observer or react to certain events that are triggered when the mutation happens.

I am using Vue.js v2

Here is a simple example I put together to illustrate the point:

live jsFiddle https://jsfiddle/w7q7b1bh/2/

The HTML below contains inlined-templates for two ponents

<script src="https://cdn.jsdelivr/npm/[email protected]/dist/vue.min.js"></script>
<script src="https://code.jquery./jquery-3.3.1.min.js"></script>
<div id="app">
  <!-- The use of inline-template is required for my solution to work -->
  <simple-counter inline-template>
    <button v-bind:style="style" v-on:click="add">clicks: {{ counter }}</button>
  </simple-counter>
  <simple-counter inline-template>
    <button v-on:click="counter += 1">{{ counter }}</button>
  </simple-counter>
</div>

<button id="mutate">Mutate</button>

The js:

// simple counter ponent
Vue.ponent('simple-counter', {
  data: function() {
    return {
      counter: 0,
      style: {
        color: 'red',
        width: '200px'
      }
    }
  },
  methods: {
    add: function() {
      this.counter = this.counter + 1;
      this.style.color = this.style.color == 'red' ? 'green' : 'red';
    }
  }
})

// create the Vue instance
var initV = () => new Vue({
  el: '#app'
});

// expose the instance for later use
window.v = initV();

// click handler that will add a new `simple-counter` template to the Vue.el scope
$('#mutate').click(function(){
    $('#app').append(`  <div is="simple-counter" inline-template>
    <button v-bind:style="style" v-on:click="add">click to add: <span class="inactive" v-bind:class="{ active: true }">{{ counter }}</span></button></div>`)
    // do something after the template is incerted
    window.v.$destroy()
    window.v = initV(); // does not work

})

As mentioned in the code, destroying the re-instantiating the Vue instance does not work, I understand why, the templates for the ponents are changed on first Vue instantiation to their final HTML, when you try and instantiate a second time, templates are not there, ponents are not mounted

I'd like to be able to find the newly added ponents after mutation and mount only those, is that possible? and how?

UPDATE: I was able to find a way to do it via instantiating a new Vue instance with el set to the specific mutated part of the DOM as opposed to the whole #app tree:

$('#mutate').click(function(){
    var appended = 
            $(`
       <div is="simple-counter" inline-template>
         <button v-bind:style="style" v-on:click="add">
           click to add: {{ counter }}
         </button>
       </div>`
      ).appendTo($('#app'));

    var newV = new Vue({el: appended[0]});
});

Seems to work, but also looks ugly and I am not sure what other implications this might have..

Use Case:

I am working on a way to write Vue.js ponents for a CMS called Adobe Experience Manager (AEM).

I write my ponents using inlined-template which gives me the advantage of SEO as well as server-side rendering using another templating language called HTL.

The way AEM authoring works is that, when a ponent is edited (via a dialog), that specific ponent is re-rendered on the server-side then injected back to the DOM to replace the old ponent, all done via Ajax and jQuery (no browser refresh).

Here is an example

AEM ponent template: <button>${properties.buttonTitle}</button>

Here is what an author might do:

  1. author visits the authoring page
  2. opens the button ponent dialog to edit
  3. changes the buttonTitle to "new button title"
  4. Saves

upon saving, an ajax is sent, the ponent HTML is re-rendered on the server and returned is the new HTML. That HTML now replaces the old HTML via jQuery (mutates the DOM)

This is fine for static ponents, but if this was a Vue.js ponent, how do I dynamically mount it while keeping other ponents mounted.

An easy solution to this is to refresh the page... but that is just bad experience... There has to be a better way.

Share Improve this question edited Feb 17, 2018 at 1:41 Ahmed Musallam asked Feb 17, 2018 at 1:18 Ahmed MusallamAhmed Musallam 9,7634 gold badges29 silver badges47 bronze badges 8
  • Instantiating the new section as a new Vue seems the right approach, but it seems like the bindings wouldn't work, because they're in the other Vue. However, possibly in a scoped slot they would. – Roy J Commented Feb 17, 2018 at 2:43
  • is there a way to mount a ponents manually and then add them to the Vue instance ? – Ahmed Musallam Commented Feb 17, 2018 at 19:22
  • I really don't know. It's very un-Vue to have something else modify the DOM and then tell Vue to take over the changes. It would be better if your server just served different data and the front end could do the DOM updates. – Roy J Commented Feb 17, 2018 at 21:56
  • I agree pletely, this will only happen on the authoring interface. For end-users, this will never happen. I think creating a new Vue instance with only the updated DOM should suffice for now. – Ahmed Musallam Commented Feb 17, 2018 at 23:12
  • 1 Might be helpful: css-tricks./… – Liam Commented Mar 6, 2018 at 20:07
 |  Show 3 more ments

2 Answers 2

Reset to default 5

Thanks to @liam I was able to find an appropriate solution to my problem

After mutating the DOM with the HTML template, keep a reference to that template's parent element

for example:

var $template = $('<div is="simple-counter" inline-template> ..rest of template here.. <div>').appendTo('#app') // app is the Vue instance el or a child of it

Now you can create a new instance of your ponent and add $template to it as the el property

if my ponent was:

var simpleCounterComponent = Vue.ponent('simple-counter', {
  data: function() {
    return {
      counter: 0,
      style: {
        color: 'red',
        width: '200px'
      }
    }
  },
  methods: {
    add: function() {
      this.counter = this.counter + 1;
      this.style.color = this.style.color == 'red' ? 'green' : 'red';
    }
  }
})

I can do:

var instance = new simpleCounterComponent({
  el: $template.get(0) // getting an HTML element not a jQuery object
});

And this way, that newly added template has bee a Vue ponent

Take a look at this fiddle for working example based on the question:

https://jsfiddle/947ojvnw/11/

One way to instantiate Vue ponents in runtime-generated HTML is:

var ComponentClass = Vue.extend({
    template: '...',
});
var instance = new ComponentClass({
    propsData: { name: value },
});
instance.$mount('#uid'); // HTML contains <... id="uid">
...
instance.$destroy(); // if HTML containing id="uid" is dropped

More here (I am not affiliated with this site)
https://css-tricks./creating-vue-js-ponent-instances-programmatically/

发布评论

评论列表(0)

  1. 暂无评论