1

What I wish to achieve is the following:

  • Use some backend logic to generate a JSON which represents a directory structure.
  • pass on this JSON to a JavaScript function to create the HTML that represents the directory structure.

The HTML I'd like to generate is the following:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
   <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">

  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

  <!-- Popper JS -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"></script>

  <!-- Latest compiled JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"></script> 
</head>
<body>
  
  <h1>Contents of "Some"</h1>

  <ul>
    <li data-toggle="collapse" data-target="#first">Collapsible </li>
      <ul id="first" class="collapse">
        <li>Lorem ipsum dolor text....</li>
        <li>Lorem ipsum dolor text....</li>
        <li>Lorem ipsum dolor text....</li>
          <li data-toggle="collapse" data-target="#sub">Collapsible</li>
            <ul id="sub" class="collapse">
              <li>Lorem ipsum dolor text....</li>
              <li>Lorem ipsum dolor text....</li>
              <li>Lorem ipsum dolor text....</li>
            </ul> 
      </ul> 
    <li> File 1</li>
    <li> File 2</li>
    <li> File 3</li>
  </ul>

 
</body>
</html>

To generate this, I am assuming my backend program can generate a JSON as follows:

    <script>
      var tree = [
        {'name': 'file1',
         'type': 'file',
         'url': 'https://www.google.com'
        },
        {'name': 'file2',
         'type': 'file',
         'url': 'https://www.google.com'
        },
        {'name': 'file1',
         'type': 'file',
         'url': 'https://www.google.com'
        },
        {'name': 'directory1',
         'type': 'folder',
         'url': 'https://www.google.com',
         'contents': [
            {'name': 'file1',
             'type': 'file',
             'url': 'https://www.google.com'
            },
            {'name': 'file2',
             'type': 'file',
             'url': 'https://www.google.com'
            },
           ]
        },
      ]

The URL in above case will be replaced by some custom URL which will enable a person to download the file. This does not matter for the problem at hand.

Now, to generate the final HTML code, I have been trying to use recursion in Javascript to construct the DOM structure. But for some reason I am unable to figure out, I end up constructing an infinite DOM. This is my current attempt.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title></title>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">

  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

  <!-- Popper JS -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"></script>

  <!-- Latest compiled JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"></script>
</head>

<body>
  <script>
    var tree = [{
        'name': 'file1',
        'type': 'file',
        'url': 'https://www.google.com'
      },
      {
        'name': 'file2',
        'type': 'file',
        'url': 'https://www.google.com'
      },
      {
        'name': 'file1',
        'type': 'file',
        'url': 'https://www.google.com'
      },
      {
        'name': 'directory1',
        'type': 'folder',
        'url': 'https://www.google.com',
        'contents': [{
            'name': 'file1',
            'type': 'file',
            'url': 'https://www.google.com'
          },
          {
            'name': 'file2',
            'type': 'file',
            'url': 'https://www.google.com'
          },
        ]
      },
    ]
  </script>

  <h1>Contents of "Some"</h1>


  <div id="tree_container">

  </div>

  <script>
    var listContentsOf = function(tree, container = 'tree_container', depth = '1') {
      console.log(tree);
      console.log(container);
      console.log(depth);

      $('#' + container).append('<ul id="' + depth + '"> </ul>');
      for (i = 0; i < tree.length; i++) {
        if (tree[i].type == 'file') {
          $('#' + depth).append('<li>' + tree[i].name + '</li>');
        } else if (tree[i].type == 'folder') {
          $('#' + depth).append('<li>' + tree[i].name + '</li>');
          subID = depth + i;
          $('#' + depth).append('<div id="' + subID + 'holder"> </div>');
          console.log(tree[i].contents);
          console.log(subID + 'holder');
          // listContentsOf(tree[i].contents, subID + 'holder', subID);
        }
      }
    };

    listContentsOf(tree);
  </script>

</body>
<

The last recursive call in the 'if else' is commented because it goes into an infinite run. Any ideas on why the infinite execution happens and how to circumvent it, would be greatly appreciated.

2 Answers 2

2

Your only mistake is not in the recursion itself, but in the scope, mainly on i variable in the loop. As you didn't defined it with var or let, it was defined in the global scope, so each iteration will share it's value. What was happening is that the second iteration was changing the previous iteration value of i to 3, so it would always enter in the third level(directory1). With var or let each i will be defined in it's own scope hence it's value will be always reliable.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title></title>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">

  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

  <!-- Popper JS -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"></script>

  <!-- Latest compiled JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"></script>
</head>

<body>
  <script>
    var tree = [{
        'name': 'file1',
        'type': 'file',
        'url': 'https://www.google.com'
      },
      {
        'name': 'file2',
        'type': 'file',
        'url': 'https://www.google.com'
      },
      {
        'name': 'file1',
        'type': 'file',
        'url': 'https://www.google.com'
      },
      {
        'name': 'directory1',
        'type': 'folder',
        'url': 'https://www.google.com',
        'contents': [{
            'name': 'file1',
            'type': 'file',
            'url': 'https://www.google.com'
          },
          {
            'name': 'file2',
            'type': 'file',
            'url': 'https://www.google.com'
          },
        ]
      },
    ]
  </script>

  <h1>Contents of "Some"</h1>


  <div id="tree_container">

  </div>

  <script>
    var listContentsOf = function(tree, container = 'tree_container', depth = '1') {
      console.log(tree);
      console.log(container);
      console.log(depth);

      $('#' + container).append('<ul id="' + depth + '"> </ul>');
      for (var i = 0; i < tree.length; i++) {
        if (tree[i].type == 'file') {
          $('#' + depth).append('<li>' + tree[i].name + '</li>');
        } else if (tree[i].type == 'folder') {
          $('#' + depth).append('<li>' + tree[i].name + '</li>');
          subID = depth + i;
          $('#' + depth).append('<div id="' + subID + 'holder"> </div>');
          console.log(tree[i].contents);
          console.log(subID + 'holder');
          listContentsOf(tree[i].contents, subID + 'holder', subID);
        }
      }
    };

    listContentsOf(tree);
  </script>

</body>
<

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much! They say scopes and the difference in each language's way of treating them has a way of haunting programmers. This just proved it! I must confess to reading about the difference between i=0 and var i=0 but since JS is not my "lingua franca" I didn't think about this at all. Thanks again.
0

So I made a little jQuery plugin that handles building an interactive nested tree from JSON data. If the parent node has child nodes, it expands the child nodes. If the child node is just data and not another parent of some sub nodes, it will display the data part in another div. Should be pretty easy to modify for other uses.

<html>
<head>
<title>A jQuery Plugin to Display an Interactive Tree from JSON data</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>

// json data
json_data = {
    "car": {
        "make": "Toyota",
        "model": "Camry",
        "year": "2012",
        "color": "black",
        "engine": {
            "type": "V6",
            "size": "3.5L",
            "horsepower": "268",
            "torque": "248"
        },
        "transmission": {
            "type": "automatic",
            "speeds": "6"
        },
        "drivetrain": {
            "type": "front wheel drive"
        },
        "safety": {
            "airbags": "front, side, and curtain",
            "brakes": "ABS",
            "tire pressure monitoring": "yes"
        },
        "warranty": {
            "basic": "36 months/36,000 miles",
            "powertrain": "60 months/60,000 miles",
            "corrosion": "60 months/Unlimited miles",
            "roadside assistance": "36 months/Unlimited miles"
        }
    },
};

// a jquery plugin to create a collapsible tree from nested json.
$.fn.jsonTree = function(json) {
    var $this = $(this);
    var $ul = $("<ul></ul>");
    $this.append($ul);
    $.each(json, function(key, value) {
        var $li = $("<li></li>");
        var $span = $("<span></span>");
        $span.text(key);
        $li.append($span);
        $ul.append($li);
        if (typeof value === "object") {
            $li.addClass("parent_li");
            $span.on("click", function(e) {
                var children = $li.find(" > ul > li");
                if (children.is(":visible")) {
                    children.hide("fast");
                } else {
                    children.show("fast");
                }
                e.stopPropagation();
            });
            $li.jsonTree(value);
        } else {
            $span.on("click", function(e) {
                $("#data-output").text(value);
                e.stopPropagation();
            });
        }
    });
};

// a function to collapse all the nodes
$.fn.collapseAll = function() {
    var $this = $(this);
    $this.find("li.parent_li").each(function() {
        var $li = $(this);
        var $children = $li.find(" > ul > li");
        $children.hide("fast");
    });
};

// a function to expand all the nodes
$.fn.expandAll = function() {
    var $this = $(this);
    $this.find("li.parent_li").each(function() {
        var $li = $(this);
        var $children = $li.find(" > ul > li");
        $children.show("fast");
    });
};
</script>

<style>
li { list-style-type: none; }
li.parent_li > span { cursor: pointer; text-decoration: underline; }
li.parent_li > span:hover {    text-decoration: none; }
li > span { margin-left: 10px; }
ul {    margin-left: 10px; }
</style>
</head>

<body>
<script>
// here is where you use the plugin
$(document).ready(function() {
    // load the JSON tree data HERE...
    $("#tree").jsonTree(json_data);
    // collapse the tree
    $("#tree").collapseAll();
});
</script>

<div id="tree"></div>
<div id="data-output"></div>

</body>
</html>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.