Home > Blockchain >  Conditionally appending a detached element
Conditionally appending a detached element

Time:09-08

I am trying to append a detached span based on certain condition like following.

const arrow = d3
    .create('span')
    .classed('arrow', true)
    .text('\u2192');

  //d3.select('body').append(()=>arrow.node());        

  const threshold = 16;

  d3.selectAll('div')
    .each(function(d, i) {
        const len = this.textContent.length;
        //console.log(len);

        d3.select(this)
            .append(function() {
                const node = arrow.node();
                const finalVal = (len <= threshold) ? node : null;
                return finalVal;

            })
    })
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
  <body>
    <div class='text1'>this is line one</div>
    <div class='text2'>this is line two</div>
    <div class='text3'>this is line three</div>
    <div class='text4'>this is line four</div>
  </body>
  <script type="text/javascript"></script>
</html>

But unfortunately, I see this error Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. in the browser console and also, I expect the span to be appended to the first div as well.

screenshot

CodePudding user response:

Try this :

  d3.selectAll('div')
    .each(function(d, i) {
        (this.textContent.length <= threshold) && d3.select(this)
            .append(function() {
                return arrow.node();
            })
    })

Explaination :

In your code, you were trying to append null hence you cought up with errors.
Here I'm checking the condition first and then appending it.

Hope this helps you!

CodePudding user response:

On top of what the other answer said ( 1), your problem here is that you believe that you just need to create the detached element once, and then you can append it again and again.

However, if you look at the MDN reference regarding appendChild (which D3 uses internally), you'll see:

If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position — there is no requirement to remove the node from its parent node before appending it to some other node. This means that a node can't be in two points of the document simultaneously. (the emphasis here is mine).

The solution here is creating the detached element inside the each loop:

   

const threshold = 16;

d3.selectAll('div')
  .each(function(d, i) {
    const len = this.textContent.length;
    if (len <= threshold) {
      const arrow = d3
        .create('span')
        .classed('arrow', true)
        .text('\u2192');
      d3.select(this)
        .append(() => arrow.node())
    }
  })
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>
  <div class='text1'>this is line one</div>
  <div class='text2'>this is line two</div>
  <div class='text3'>this is line three</div>
  <div class='text4'>this is line four</div>
</body>
<script type="text/javascript"></script>

</html>

But using d3.create in this case is a bit pointless, because you can simply append the element you want the regular way. If you want to keep using d3.create, you can create the detached element once and append clones of it:

d3.select(this).append(() => arrow.node().cloneNode(true))

Here's the demo:

const arrow = d3
  .create('span')
  .classed('arrow', true)
  .text('\u2192');

const threshold = 16;

d3.selectAll('div')
  .each(function(d, i) {
    const len = this.textContent.length;
    if (len <= threshold) {
      d3.select(this)
        .append(() => arrow.node().cloneNode(true))
    }
  })
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>
  <div class='text1'>this is line one</div>
  <div class='text2'>this is line two</div>
  <div class='text3'>this is line three</div>
  <div class='text4'>this is line four</div>
</body>
<script type="text/javascript"></script>

</html>

  • Related