Yury Stanev
Yury Stanev

Reputation: 509

can't output html content to a page from foreach callback in javascript

I have a function that loops over each element of the array and outputs the html content to the page while modifying some properties with elements from the array. I'm using forEach to loop over elements of the array and innerHTML to print content to the page.

I keep getting the following error: netdata.html:41 Uncaught TypeError: Cannot set property 'innerHTML' of null. I'm not sure what's causing it as I do have an element with the proper id, <div id="info"></div>

const clusterIPs = [
      { name: "eco-cluster-1", ip: "192.168.50.101" },
      { name: "eco-cluster-2", ip: "192.168.50.102" },
      { name: "eco-cluster-3", ip: "192.168.50.103" },
      { name: "eco-cluster-4", ip: "192.168.50.104" },
      { name: "eco-cluster-6", ip: "192.168.50.105" },
      { name: "eco-cluster-7", ip: "192.168.50.106" },
      { name: "eco-cluster-8", ip: "192.168.50.107" },
      { name: "eco-cluster-9", ip: "192.168.50.108" },
      { name: "eco-cluster-10", ip: "192.168.50.110" },
      { name: "eco-cluster-11", ip: "192.168.50.111" },
      { name: "eco-cluster-12", ip: "192.168.50.112" },
      { name: "eco-cluster-13", ip: "192.168.50.113" }
    ];

    clusterIPs.forEach(pc => {
      console.log(pc);

      //   $("body").html

      document.getElementById("info").innerHTML = `
        <div class="container-fluid">
        <div><h1>${pc.name}</h1></div>
        <div class="row">
        <div class="col-md">
          <div
            data-title="CPU"
            data-netdata="system.cpu"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
        <div class="col-md">
          <div
            data-title="RAM"
            data-netdata="system.ram"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
      </div>
      <div class="row">
        <div class="col-md">
          <div
            data-title="Disk I/O"
            data-netdata="system.io"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
        <div class="col-md">
          <div
            data-title="Network Bandwidth"
            data-netdata="system.net"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
      </div>
    </div>
        `;
    });
    //   console.log(clusterIPs)

Upvotes: 0

Views: 70

Answers (2)

Miaplacidus
Miaplacidus

Reputation: 545

The div#info element is not available when the script runs. There are several ways to solve this:

  1. Add the DOMContentLoaded event listener to the document object, then in the callback run the existing script.
  2. Add the <script> tag containing the existing script AFTER the div#info element.
  3. Refactor the script you wrote. I would personally write this:

Component Representing HTML Element

class Component {
  constructor() {}
  get element() {
    if(this._element) return this._element
    this._element = document.createElement('div')
    this._element.setAttribute('id', 'info')
    return this._element
  }
  get data() { return [
    { name: "eco-cluster-1", ip: "192.168.50.101" },
    { name: "eco-cluster-2", ip: "192.168.50.102" },
    { name: "eco-cluster-3", ip: "192.168.50.103" },
    { name: "eco-cluster-4", ip: "192.168.50.104" },
    { name: "eco-cluster-6", ip: "192.168.50.105" },
    { name: "eco-cluster-7", ip: "192.168.50.106" },
    { name: "eco-cluster-8", ip: "192.168.50.107" },
    { name: "eco-cluster-9", ip: "192.168.50.108" },
    { name: "eco-cluster-10", ip: "192.168.50.110" },
    { name: "eco-cluster-11", ip: "192.168.50.111" },
    { name: "eco-cluster-12", ip: "192.168.50.112" },
    { name: "eco-cluster-13", ip: "192.168.50.113" }
  ] }
  get template() { return (pc) => `
    <div class="container-fluid">
      <div><h1>${pc.name}</h1></div>
      <div class="row">
        <div class="col-md">
          <div
            data-title="CPU"
            data-netdata="system.cpu"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
        <div class="col-md">
          <div
            data-title="RAM"
            data-netdata="system.ram"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
      </div>
      <div class="row">
        <div class="col-md">
          <div
            data-title="Disk I/O"
            data-netdata="system.io"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
        <div class="col-md">
          <div
            data-title="Network Bandwidth"
            data-netdata="system.net"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
      </div>
    </div>
  ` }
  insertElement() {
    document.querySelector('body')
      .insertAdjacentElement('afterbegin', this.element)
    return this
  }
  clearElement() {
    this.element.innerHTML = ''
    return this
  }
  render() {
    if(!this.element.parentElement) {
      this.insertElement()
    } else {
      this.clearElement()
    }
    this.data.forEach((dataItem) => {
      let template = document.createRange().createContextualFragment(
        this.template(dataItem)
      )
      this.element.appendChild(template)
    })
    return this
  }
}

Instantiate Component

document.addEventListener('DOMContentLoaded', (event) => {
  let component = new Component()
  component.render()
})

Upvotes: 0

mplungjan
mplungjan

Reputation: 178350

You are

  1. Adding HTML before the container exists.
  2. Replacing HTML each time in the loop instead of appending

To use innerHTML you can do

window.addEventListener("load",function() {
  const container = document.getElementById("info");
  clusterIPs.forEach(pc => container.innerHTML += `....`);
})

Or use jQuery to append since you already load it

$(function() { // on page load
  const $container = $("#info");
  $.each(clusterIPs,(i, pc) => {
    $container.append(`....`);
  });
});

    const clusterIPs = [
      { name: "eco-cluster-1", ip: "192.168.50.101" },
      { name: "eco-cluster-2", ip: "192.168.50.102" },
      { name: "eco-cluster-3", ip: "192.168.50.103" },
      { name: "eco-cluster-4", ip: "192.168.50.104" },
      { name: "eco-cluster-6", ip: "192.168.50.105" },
      { name: "eco-cluster-7", ip: "192.168.50.106" },
      { name: "eco-cluster-8", ip: "192.168.50.107" },
      { name: "eco-cluster-9", ip: "192.168.50.108" },
      { name: "eco-cluster-10", ip: "192.168.50.110" },
      { name: "eco-cluster-11", ip: "192.168.50.111" },
      { name: "eco-cluster-12", ip: "192.168.50.112" },
      { name: "eco-cluster-13", ip: "192.168.50.113" }
    ];


$(function() { // on page load
  const $container = $("#info");
  $.each(clusterIPs,(i, pc) => {
    $container.append(`
        <div class="container-fluid">
        <div><h1>${pc.name}</h1></div>
        <div class="row">
        <div class="col-md">
          <div
            data-title="CPU"
            data-netdata="system.cpu"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
        <div class="col-md">
          <div
            data-title="RAM"
            data-netdata="system.ram"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
      </div>
      <div class="row">
        <div class="col-md">
          <div
            data-title="Disk I/O"
            data-netdata="system.io"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
        <div class="col-md">
          <div
            data-title="Network Bandwidth"
            data-netdata="system.net"
            data-chart-library="dygraph"
            data-after="-600"
            data-host="http://${pc.ip}:19999/"
          ></div>
        </div>
      </div>
    </div>`)
  });
});
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />

<div id="info"></div>

<script type="text/javascript" src="http://localhost:19999/dashboard.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

Upvotes: 1

Related Questions