Passing data from the phoenix template to the JS file

Hello, I am trying to pass the data from the template to a javascript file but without success. I am doing it the way that worked for me last time. Here is the code:

 <div id="bar_chart_verification_statistics"></div>


<script type="text/javascript">
  verificationStatistics = <%= raw @verification_statistics %>;
  console.log(verificationStatistics); // For debugging
</script>

The object in the console looks like this:

{
    "unverified_count": 66,
    "unverified_percentage": 96,
    "verified_count": 3,
    "verified_percentage": 4
}

However, based on the console output, the script stops here:

var data = [
  {group: "Verified", count: verificationStatistics.verified_count, percentage: verificationStatistics.verified_percentage},
  {group: "Unverified", count: verificationStatistics.unverified_count, percentage: verificationStatistics.unverified_percentage}
];

Any console.log after var data is not printed. The error from the console is as follows:

verificationStatistics is not defined

I did everything the same way I did for another JS script. I got the same error from that other file about not defined variable, but it works.

Here is the full script:

import * as d3 from 'd3';

// Define SVG area dimensions
var svgWidth = 500;
var svgHeight = 500;

// Define the chart's margins as an object
var margin = { top: 20, right: 20, bottom: 60, left: 40 },
    width = svgWidth - margin.left - margin.right,
    height = svgHeight - margin.top - margin.bottom;

// Append an SVG wrapper, append an SVG group that will hold our chart,
// and shift the latter by left and top margins.
var svg = d3.select("#bar_chart_verification_statistics")
  .append("svg")
  .attr("width", svgWidth)
  .attr("height", svgHeight)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var x = d3.scaleBand().range([0, width]).padding(0.2);
var y = d3.scaleLinear().range([height, 0]);

var data = [
  {group: "Verified", count: verificationStatistics.verified_count, percentage: verificationStatistics.verified_percentage},
  {group: "Unverified", count: verificationStatistics.unverified_count, percentage: verificationStatistics.unverified_percentage}
];

// Scale the range of the data in the domains
x.domain(data.map(function(d) { return d.group; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);

// append the rectangles for the bar chart
svg.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { 
      var xValue = x(d.group);
      console.log('xValue:', xValue);
      return xValue; 
    })
    .attr("width", x.bandwidth())
    .attr("y", function(d) { 
      var yValue = y(d.count);
      console.log('yValue:', yValue);
      return yValue; 
    })
    .attr("height", function(d) { return height - y(d.count); });

// add the x Axis
svg.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x));

// add the y Axis
svg.append("g")
    .call(d3.axisLeft(y));

// add labels
svg.selectAll(".text")
  .data(data)
  .enter()
  .append("text")
  .attr("class", "text")  // add class to text
  .text(function(d) {
    var textValue = d.count + " (" + d.percentage + "%)";
    console.log('textValue:', textValue);
    return textValue;
  })
  .attr("x", function(d, i) {
    var xValue = x(d.group) + x.bandwidth() / 2;
    console.log('text xValue:', xValue);
    return xValue;
  })
  .attr("y", function(d) {
    var yValue = y(d.count) - 5;
    console.log('text yValue:', yValue);
    return yValue;
  })
  .attr("text-anchor", "middle");  // center the text

As you can see, it should render a bar chart in the div but does not return anything. Does someone have any idea what could be wrong here?

Are you using Phoenix Liveview or a Phoenix controller? Either way you should not use a dynamic <script> tag. With Liveview, you should use push_event, and with a controller you should do a Ajax request to get the data in json.

I am not using Lieview. It’s from a Phoenix controller.
First, I get the data from querying the DB; then, I encode it.
Relevant part:

   json_verification_statistics = Jason.encode!(verification_statistics)

    render(conn, "page_name.html",
      verification_statistics: json_verification_statistics
    )

Ok, the recommended way is to have a json endpoint, and let the front end fetch it.

Is your d3 code located in the default app.js file? And is there a defer attribute on the <script> tag for that JS? For example, for your app.js you should see a tag like this in the <head>:

<script defer phx-track-static type="text/javascript" src="/assets/app.js">

Note the defer attribute - it will ensure your JS is only executed after the DOM is parsed. Without it, it’ll execute immediately, and therefore will not have access to window.verificationStatistics defined later in the HTML. So first off I would ensure your script is imported with the defer attribute.

Otherwise, does wrapping your code in a DOMContentLoaded event handler fix the issue? For instance:

import * as d3 from 'd3';

document.addEventListener("DOMContentLoaded", () => {
  // Define SVG area dimensions
  var svgWidth = 500;
  var svgHeight = 500;
  // ...
});

@derek-zhou and @Cless. I changed it a bit and added the json endpoint

Now it gets the data correctly. Right now, I am having a problem with generating the chart. I am debugging that. Anyway, thank you guys!

1 Like

This means your variable is not defined… and it’s true because You did not use const, or let (or var)

When You use this You probably need to decode it JS side with JSON.parse()