简体   繁体   中英

Adding HTML element to a vue template

I'm using a chart component (Chartist) that requires a HTML element to use as a parent when rendering the SVG.

The elements that the chart can use is generated during a v-for loop, which means that they are not added to the DOM at the time of the chart rendering.

The code looks something like this (in the vue):

<div v-for="chart in charts">
  <h1>{{chart.Title}}</h1>
  <div class="ct-chart" :id="'chart-' + chart.name"></div>
  {{generateChart('#chart-' + chart.name, chart.Data}}
  <div>
    <span v-for="l in legend" :class="chart.ClassName">{{l.DisplayName}}</span>
</div>

In the component:

generateChart(chartName: string, data: IChartData) {
    /* More code here */

    //doesn't get added (can't find html node in dom)
    new Chartist.Line(chartName, data, options);

    // this worked
    //var element = document.createElement("DIV");
    //element.className = "ct-chart";
    //document.body.appendChild(element);
    //new Chartist.Line(element, data, options);

}

Which results in that Chartist fails on querySelectorAll .

If I on the other hand generate an element using document.createElement and attach it to the html body the generation works fine.

Summary:

I want to render a chart which requires a DOM element for the rendering. But since Vue renders everything in it's virtual DOM, there is no element available when Vue renders the view (including the chart).

Question:

How can I tell vue to add a pre-created HTML element? Or how can I defer the chartist generation until vue have added everything to the real DOM?

Workaround:

I'm using a timeout ( setTimeout ) to defer the chart rendering so that Vue can complete before I invoke the chart rendering function.

First and foremost, don't use function in a template like this . Template is converted into render function and that function is executed whenever something changes - in this case that means you will be creating new chartist objects every time the template is rendered ....which is something you don't want.

What you want is to render DOM nodes (without any charts) first and use mounted hook to initialize each chart after the DOM is ready:

mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}

If you need DOM elements references, you can use refs

Also be aware that most libs which create DOM element dynamically (like Chartist) almost always need some cleanup code to avoid memory leaks

Or just use some Vue wrapper, for example vue-chartist

As far as I understand the question you don't want to load the div having the class of ".ct-chart" untill the DOM has completely loaded.

What you can do is that add a v-if to your vue component and when the DOM is fully loaded you can call your generateChart() function.

new Vue({
  el: '#app',
  data: {
   domloaded: false
  },
  created() {
     window.addEventListener('load', function () {
      domloaded = true;
   });
  }
}

You can then add a v-if like this or maybe use lifecycle hooks like mounted() or created()

<div v-for="chart in charts">
 <h1>{{chart.Title}}</h1>
 <div v-if="domloaded" class="ct-chart" :id="'chart-' + chart.name"></div>
  {{generateChart('#chart-' + chart.name, chart.Data)}}
 <div>
 <span v-for="l in legend" :class="chart.ClassName">{{l.DisplayName}}</span>
</div>

  

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM