Search code examples
meteormeteor-blazespacebars

Rendering static HTML in Meteor/Blaze


I have been trying to include static HTML+JavaScript inside the Meteor / Telescope (telesc.pe) application, and have not been able to get things working with Blaze.

To create a simpler case than my original, I have tried inserting a simple HTML + javascript code block (very basic D3.js visualization that creates a circle) in via Meteor which is below. This HTML block is stored inside a collection in MongoDB and accessed using the template below

<script type="text/javascript" src="http://mpld3.github.io/js/mpld3.v0.2.js"></script>
  <h1>Circle</h1>
    <div id="viz"></div>
  <h1>/Circle</h1>
    <script type="text/javascript">

    var sampleSVG = d3.select("#viz")
        .append("svg")
        .attr("width", 100)
        .attr("height", 100);

    sampleSVG.append("circle")
        .style("stroke", "gray")
        .style("fill", "white")
        .attr("r", 40)
        .attr("cx", 50)
        .attr("cy", 50)
        .on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
        .on("mouseout", function(){d3.select(this).style("fill", "white");});

    </script>

I've been including it in Meteor as an unescaped tag. e.g.

<template name="post_body">
  {{{ htmlBody }}}
</template>

The HTML inside the snippet above renders correctly (the text Circle and /Circle), but it does not appear to try to load any of the javascript elements that I have included.

I am completely aware that this is not an optimal way to load a visualization into a Meteor app, but will most likely need to do it this way as the (more complicated) visualizations I'm using are generated statically using an external app.

Any help on how to get this working would be much appreciated!


Solution

  • You can’t use <script> tags inside Meteor templates. Meteor parses your template to render it into the DOM as DOM objects, and doesn’t execute any inline JavaScript.

    That said, it’s quite easy to achieve what you’re trying to do. First, take this line:

    <script type="text/javascript" src="http://mpld3.github.io/js/mpld3.v0.2.js"></script>
    

    And put it inside the <head> of your index.html file, or whichever HTML file is the entry point for your app.

    Next, take the code that was in your inline script block and put it into the rendered callback for your template:

    Template.post_body.rendered = function() {
        var sampleSVG = d3.select("#viz")
            .append("svg")
            .attr("width", 100)
            .attr("height", 100);
    
        sampleSVG.append("circle")
            .style("stroke", "gray")
            .style("fill", "white")
            .attr("r", 40)
            .attr("cx", 50)
            .attr("cy", 50)
            .on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
            .on("mouseout", function(){d3.select(this).style("fill", "white");});
    };
    

    It will execute after the template has been rendered into the DOM, as you’d want. For performance improvements, you can look into replacing d3.select with this.find or this.findAll within rendered, to limit the DOM searching to just the template context. See http://docs.meteor.com/#/full/template_find.

    If you’re literally dealing with a terrible HTML block straight from your database, that has the <script> tags from your sample, you’ll need to parse it as a string first. Use a regex to find the src of the first script tag (I’ll let you find the many other SO answers on how to do that) and use jQuery’s $.getScript to load that script dynamically. Use another regex to pull out the inline script block, and pass it as a callback to $.getScript. As much as I hate saying it, you’ll have to use eval:

    Template.post_body.rendered = function() {
        $.getScript(urlThatYouParsed, function() { eval(codeThatYouParsed); });
    };