d3.jstemplate-literals

d3js inline event handler within template literal


In the example below there is an inline event handler that is not working, because there is something wrong with escaping characters. In general inline handlers are bad practice and difficult to manage. An other option is to attach the handler properly using Javascript instead (see comment part of the snippet). But then I have to write the template in a way that contradicts the usual HTML standard and thus makes it less readable. Is there another option?

Running code:

   

    /*
    const divElement = document.createElement('div');
    divElement.setAttribute('customId', node.data.customId);
    divElement.style.backgroundColor = 'aqua';
    divElement.style.width = `${node.width}px`;
    divElement.style.height = `${node.height}px`;
    divElement.textContent = node.data.customName;
    const innerDiv = document.createElement('div');
    innerDiv.textContent = 'klick'; 
    innerDiv.addEventListener('click', () => {
      test(stringObj);
    });
    divElement.appendChild(innerDiv); 
*/

const data = [
    { customId: 1, customParentId: null, customName: 'node1' },
    { customId: 2, customParentId: 1, customName: 'node2' },
    { customId: 3, customParentId: 1, customName: 'node3' },
  ];

    chart = new d3.OrgChart()
      .nodeId((dataItem) => dataItem.customId)
      .parentNodeId((dataItem) => dataItem.customParentId)
      .nodeWidth((node) => 100)
      .nodeHeight((node) => 100)
      .nodeContent((node) => {
      const obj = {id: node.data.customId, name: node.data.customName};
      const stringObj = JSON.stringify(obj);
        return `<div customId="${node.data.customId}" 
            style="background-color:aqua;width:${node.width}px;height:${node.height}px"
          > 
          <div onclick="test('${stringObj}')">klick</div>
               ${node.data.customName}
           </div>`;
      })
      .container('.chart-container')
      .data(data).render();
function test(data){
  const dataObj = JSON.parse(data);
  console.log(data);
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-org-chart@3.0.1"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-flextree@2.1.2/build/d3-flextree.js"></script>

<div class="chart-container"></div>


Solution

  • Your primary issue is that the nodeContent method can only return a string, which leads to the hacks you have in place. It would be nice if it could return an html element so that you could build it out programmatically. This feature (see here and here) has been requested but doesn't look implemented in the library.

    The second github issue I linked details a workaround by using the nodeUpdate method. I've implemented it using your code below. Doesn't look too horrible.

    const data = [
        { customId: 1, customParentId: null, customName: 'node1' },
        { customId: 2, customParentId: 1, customName: 'node2' },
        { customId: 3, customParentId: 1, customName: 'node3' },
      ];
    
        chart = new d3.OrgChart()
          .nodeId((dataItem) => dataItem.customId)
          .parentNodeId((dataItem) => dataItem.customParentId)
          .nodeWidth((node) => 100)
          .nodeHeight((node) => 100)
          .nodeContent((node) => '<div></div>')
          .nodeUpdate(function (node) {
            const div = d3.select(this).select('foreignObject>div>div');
            div.attr("customId", node.data.customId)
              .style('background-color', 'aqua')
              .style('width', node.width + 'px')
              .style('height', node.height + 'px')
              .append('div')
              .html(node.data.customName)
              .on("click", test);
          })
          .container('.chart-container')
          .data(data).render();
          
    function test(e,data){
      console.log(data);
    }
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/d3-org-chart@3.0.1"></script>
    <script src="https://cdn.jsdelivr.net/npm/d3-flextree@2.1.2/build/d3-flextree.js"></script>
    
    <div class="chart-container"></div>