Javascript  

 

 

 

 

 

JSON Viewer

 

This code is created first by chatGPT on Feb 10 2023 (meaning using chatGPT 3.5) upto Second Upgrade and then modified a little bit my me. From Fourth Upgrade, I did with chatGPT (gpt 4) in paid version (chatGPT Pro) on Mar 15 2023.

NOTE : Workign with chatGPT (gpt 4), everything went so smoothly and almost no debugging activity was needed.

 

 

 

First Version

 

The initial request that I put into chatGPT is as follows :

 

NOTE : This is pretty complicated requirement because it has to parse JSON and display it in the tree stucture. I haven't got any code from chatGPT which exactly match this requirement. I tried the same requirement several times as new sessions and had several different versions of the code (completely different code whenever I requested in different chatGPT session). I picked one of them which is the closed to my requirement even though it does not perpectly match my expectation.

For now, I am happy that chatGPT can create a complicated program like this.

Write a javascript and html with the following requirement

1. create a menu bar at the top of the page.

2. Put an inputbox named 'fileName' with type="file", a button named 'Update', button named 'ExpandAll', button named 'CollapseAll' and button named 'Clear'. Make these components aligned left. Assume that the specified file has contents in JSON format

3. Split the page vertically in two parts.

4. When 'fileOpen' button is clicked, it shows File Open Dialogbox and allow user to navigate and select the file name. When a file is selcted, the filename and path should be printed into fileName inputbox

5. When 'Update' button is clicked`, display the contents of the file name(a json format) in fileName on the left side of the page in the form of tree. The tree should be collapsable, expandable and editable. It would be the best if you implement this with built in Javascript. If not possible, you may use jquery library

6. When 'ExpandAll' button is clicked`,Expand the tree on the left as much as possible.

7. When 'CollapseAll' button is clicked`,Collapse the items to the root.

8. When 'Clear' button is clicked`, remove all the items on the tree

9. When one of the item on an item of the opened file on the left, the name of the selected item should be printed out on the right side of the page.

10. When one of the items is doubleclicked, make it editable by user and the edit box should disappear when enter key is pressed.

11. Write all the event handler as a separate functions

12. Write html, css, javascript in separate files(name of css file is Jsonviewer.css and the name of the javascript file is Jsonviewer.js)

NOTE : It is not guaranteed that you would have the same code as I got since chatGPT produce the answers differently depending on the context. And it may produce the different answers everytime you ask even with the exact the same question.

NOTE : If you don't have any of your own idea for the request, copy my request and paste it into the chatGPT and put additional requests based on the output for the previous request. I would suggest to create a new thread in the chatGPT and put my request and then continue to add your own request.

 

Usage : Click on [Choose File] button and select any file which has the contents in JSON format. It will show the contents in tree format as shown below. (NOTE : If you want to try with the live demo, check this out)

 

 

JsonViewer.html

<!DOCTYPE html>

<html>

<head>

  <link rel="stylesheet" type="text/css" href="JsonViewer.css">

  <script src="JsonViewer.js"></script>

</head>

<body>

  <div id="menuBar">

    <input type="file" id="fileName"  accept=".txt">

    <button id="update">Update</button>

    <button id="expandAll">Expand All</button>

    <button id="collapseAll">Collapse All</button>

    <button id="clear">Clear</button>

  </div>

  <div id="leftContainer"></div>

  <div id="rightContainer"></div>

</body>

</html>

 

JsonViewer.css

#menuBar {

  display: flex;

  justify-content: left;

  padding: 10px;

  background-color: lightgray;

}

 

#leftContainer {

  float: left;

  width: 50%;

  height: 100%;

}

 

#rightContainer {

  float: right;

  width: 50%;

  height: 100%;

}

 

 

JsonViewer.js

document.addEventListener("DOMContentLoaded", function() {

 

      var data;

      

      function update() {

        var fileInput = document.getElementById("fileName");

        var files = fileInput.files;

        if (files.length === 0) {

          alert("Please select a file.");

          return;

        }

        var file = files[0];

        var reader = new FileReader();

        reader.onload = function() {

          // parse the contents of the file as JSON and display it in the left container

          var json = JSON.parse(reader.result);

          data = json;

          displayJson(json);

        };

        reader.readAsText(file);

      }

 

      function displayJson(json) {

        var leftContainer = document.getElementById("leftContainer");

        leftContainer.innerHTML = '';

      

        // Create a recursive function to display the JSON contents as a tree

        function createNode(node, parentNode) {

          var nodeDiv = document.createElement("div");

          nodeDiv.style.marginLeft = "10px";

      

          for (var key in node) {

            var keyDiv = document.createElement("div");

            var valueDiv = document.createElement("div");

      

            keyDiv.innerHTML = key + ":";

            keyDiv.style.fontWeight = "bold";

            valueDiv.innerHTML = JSON.stringify(node[key]);

      

            nodeDiv.appendChild(keyDiv);

            nodeDiv.appendChild(valueDiv);

      

            if (typeof node[key] === "object") {

              createNode(node[key], valueDiv);

            }

          }

          parentNode.appendChild(nodeDiv);

        }

      

        createNode(json, leftContainer);

      }

      

      function expandAll() {

        var leftContainer = document.getElementById("leftContainer");

        var nodeDivs = leftContainer.getElementsByTagName("div");

      

        for (var i = 0; i < nodeDivs.length; i++) {

          nodeDivs[i].style.display = "block";

        }

      }

      

      function collapseAll() {

        var leftContainer = document.getElementById("leftContainer");

        var nodeDivs = leftContainer.getElementsByTagName("div");

      

        for (var i = 0; i < nodeDivs.length; i++) {

          nodeDivs[i].style.display = "none";

        }

      }

      

      function clear() {

        document.getElementById("leftContainer").innerHTML = '';

      }

      

      function selectItem() {

        // Code to print the name of the selected item on the right side of the page

        var selectedItem = event.target.innerHTML;

        document.getElementById("rightContainer").innerHTML = selectedItem;

      }

    

      function editItem() {

        var item = event.target;

        var value = item.innerHTML;

        var index = Array.from(item.parentNode.children).indexOf(item);

      

        item.innerHTML = "<input type='text' value='" + value + "'>";

      

        var input = item.children[0];

        input.addEventListener("keydown", function(e) {

          if (e.key === "Enter") {

            item.innerHTML = input.value;

            if (index >= 0 && index < data.length) {

              data[index] = input.value;

            }

          }

        });

      }

      

      document.getElementById("update").addEventListener("click", update);

      document.getElementById("expandAll").addEventListener("click", expandAll);

      document.getElementById("collapseAll").addEventListener("click", collapseAll);

      document.getElementById("clear").addEventListener("click", clear);

      document.getElementById("fileName").addEventListener("change", update);

      

      var leftContainer = document.getElementById("leftContainer");

      leftContainer.addEventListener("click", selectItem);

      leftContainer.addEventListener("dblclick", editItem);

      

    } );

 

 

 

First Upgrade

 

In the first version, there is one thing which I didn't like.  The code display the key and value of JSON data in separate lines as shown below. This make it harder to read the data (at least for me) and make the output too long. So I wanted to change this to display the key and value on the same line.

 

 

I want to ask chatGPT to revise the code to make changes in output format as I like. But there is a problem. The chatGPT session for the initial code generation ended and now chatGPT does not have any context to this program.

 

What am I supposed to do in this case ?

 

There are roughly two things you can think of. The first thing is to copy the whole javascript code and paste it into chatGPT prompt and put addional request. However, based on my experience this tend not to give proper output. Sometimes it deny the request (often with the execuse of too complicated request) or in more case it tend to write the whole code again which would generate many errors.

So I tried with a little bit different approach. I copied a specific function that I want to make changes and pasted it into chatGPT with additional requirement. It may respond with a little bit of out of context since it does not have the entire code, but in most cases I got pretty satisfactory from this approach.

 

For now, I am happy that chatGPT can create a complicated program like this.

 

In this specific example, my request was as follows :

can you rewrite the following function so that it prints key and value in the same line in the form of key:value ?      

function displayJson(json) {

        var leftContainer = document.getElementById("leftContainer");

        leftContainer.innerHTML = '';

      

        // Create a recursive function to display the JSON contents as a tree

        function createNode(node, parentNode) {

          var nodeDiv = document.createElement("div");

          nodeDiv.style.marginLeft = "10px";

      

          for (var key in node) {

            var keyDiv = document.createElement("div");

            var valueDiv = document.createElement("div");

      

            keyDiv.innerHTML = key + ":";

            keyDiv.style.fontWeight = "bold";

            valueDiv.innerHTML = JSON.stringify(node[key]);

      

            nodeDiv.appendChild(keyDiv);

            nodeDiv.appendChild(valueDiv);

      

            if (typeof node[key] === "object") {

              createNode(node[key], valueDiv);

            }

          }

          parentNode.appendChild(nodeDiv);

        }

      

        createNode(json, leftContainer);

      }

 

The result is as shown below. I think this is pretty satisfactory for me. It is much more readable and the output format gets more readable.

 

Following is the revised version of the javascript. The changed part is highlighted in red.

(NOTE : I didn't get this result as single shot. I tried with the same request in a few times and did a little bit of troubleshooting until I get this).

JsonViewer.js

document.addEventListener("DOMContentLoaded", function() {

 

      var data;

      

      function update() {

        var fileInput = document.getElementById("fileName");

        var files = fileInput.files;

        if (files.length === 0) {

          alert("Please select a file.");

          return;

        }

        var file = files[0];

        var reader = new FileReader();

        reader.readAsText(file);

        reader.onload = function() {

          // parse the contents of the file as JSON and display it in the left container

          var json = JSON.parse(reader.result);

          data = json;

          displayJson(json);

        };

        //reader.readAsText(file);

      }

 

      function displayJson(json) {

        var leftContainer = document.getElementById("leftContainer");

        leftContainer.innerHTML = '';

      

        // Create a recursive function to display the JSON contents as a tree

        function createNode(node, parentNode) {

          var nodeDiv = document.createElement("div");

          nodeDiv.style.marginLeft = "10px";

      

          for (var key in node) {

            var keyValuePairDiv = document.createElement("div");

      

            keyValuePairDiv.innerHTML = key + ":" + JSON.stringify(node[key]);

            keyValuePairDiv.style.fontWeight = "bold";

      

            nodeDiv.appendChild(keyValuePairDiv);

      

            if (typeof node[key] === "object") {

              createNode(node[key], keyValuePairDiv);

            }

          }

          parentNode.appendChild(nodeDiv);

        }

      

        createNode(json, leftContainer);

      }

      

      

      function expandAll() {

        var leftContainer = document.getElementById("leftContainer");

        var nodeDivs = leftContainer.getElementsByTagName("div");

      

        for (var i = 0; i < nodeDivs.length; i++) {

          nodeDivs[i].style.display = "block";

        }

      }

      

      function collapseAll() {

        var leftContainer = document.getElementById("leftContainer");

        var nodeDivs = leftContainer.getElementsByTagName("div");

      

        for (var i = 0; i < nodeDivs.length; i++) {

          nodeDivs[i].style.display = "none";

        }

      }

      

      function clear() {

        document.getElementById("leftContainer").innerHTML = '';

      }

      

      function selectItem() {

        // Code to print the name of the selected item on the right side of the page

        var selectedItem = event.target.innerHTML;

        document.getElementById("rightContainer").innerHTML = selectedItem;

      }

    

      function editItem() {

        var item = event.target;

        var value = item.innerHTML;

        var index = Array.from(item.parentNode.children).indexOf(item);

      

        item.innerHTML = "<input type='text' value='" + value + "'>";

      

        var input = item.children[0];

        input.addEventListener("keydown", function(e) {

          if (e.key === "Enter") {

            item.innerHTML = input.value;

            if (index >= 0 && index < data.length) {

              data[index] = input.value;

            }

          }

        });

      }

      

      document.getElementById("update").addEventListener("click", update);

      document.getElementById("expandAll").addEventListener("click", expandAll);

      document.getElementById("collapseAll").addEventListener("click", collapseAll);

      document.getElementById("clear").addEventListener("click", clear);

      document.getElementById("fileName").addEventListener("change", update);

      

      var leftContainer = document.getElementById("leftContainer");

      leftContainer.addEventListener("click", selectItem);

      leftContainer.addEventListener("dblclick", editItem);

      

    } );

 

 

 

Second Upgrade

 

Now I found another point which makes me unhappy with. As you see, the red part and blue part represents the same data and I wanted to remove the red part.

 

 

I tried with the same approach for the revision as explained above. In this specific example, my request was as follows :

can you change following code in such a way that only the final node shows in the form of 'key:value' and all other nodes in the form of 'key:' ?

function displayJson(json) {

        var leftContainer = document.getElementById("leftContainer");

        leftContainer.innerHTML = '';

      

        // Create a recursive function to display the JSON contents as a tree

        function createNode(node, parentNode) {

          var nodeDiv = document.createElement("div");

          nodeDiv.style.marginLeft = "10px";

      

          for (var key in node) {

            var keyValuePairDiv = document.createElement("div");

      

            keyValuePairDiv.innerHTML = key + ":" + JSON.stringify(node[key]);

            keyValuePairDiv.style.fontWeight = "bold";

      

            nodeDiv.appendChild(keyValuePairDiv);

      

            if (typeof node[key] === "object") {

              createNode(node[key], keyValuePairDiv);

            }

          }

          parentNode.appendChild(nodeDiv);

        }

      

        createNode(json, leftContainer);

      }

 

 

This is what I got as final result.

 

 

 

Following is the revised version of the javascript. The changed part is highlighted in red.

(NOTE : I didn't get this result as single shot. I tried with the same request in a few times and did a little bit of troubleshooting until I get this).

JsonViewer.js

document.addEventListener("DOMContentLoaded", function() {

 

      var data;

      

      function update() {

        var fileInput = document.getElementById("fileName");

        var files = fileInput.files;

        if (files.length === 0) {

          alert("Please select a file.");

          return;

        }

        var file = files[0];

        var reader = new FileReader();

        reader.readAsText(file);

        reader.onload = function() {

          // parse the contents of the file as JSON and display it in the left container

          var json = JSON.parse(reader.result);

          data = json;

          displayJson(json);

        };

        //reader.readAsText(file);

      }

 

      function displayJson(json) {

        var leftContainer = document.getElementById("leftContainer");

        leftContainer.innerHTML = '';

      

        function createNode(node, parentNode) {

          var nodeDiv = document.createElement("div");

          nodeDiv.style.marginLeft = "10px";

      

          for (var key in node) {

            var keyValuePairDiv = document.createElement("div");

            keyValuePairDiv.style.fontWeight = "bold";

            keyValuePairDiv.innerHTML = key + ":";

      

            if (typeof node[key] === "object" && Object.keys(node[key]).length > 0) {

              createNode(node[key], keyValuePairDiv);

            } else {

              var valueDiv = document.createElement("div");

              valueDiv.style.display = "inline";

              valueDiv.innerHTML = JSON.stringify(node[key]);

              keyValuePairDiv.appendChild(valueDiv);

            }

      

            nodeDiv.appendChild(keyValuePairDiv);

          }

      

          parentNode.appendChild(nodeDiv);

        }

      

        createNode(json, leftContainer);

      }

      

          

      

      function expandAll() {

        var leftContainer = document.getElementById("leftContainer");

        var nodeDivs = leftContainer.getElementsByTagName("div");

      

        for (var i = 0; i < nodeDivs.length; i++) {

          nodeDivs[i].style.display = "block";

        }

      }

      

      function collapseAll() {

        var leftContainer = document.getElementById("leftContainer");

        var nodeDivs = leftContainer.getElementsByTagName("div");

      

        for (var i = 0; i < nodeDivs.length; i++) {

          nodeDivs[i].style.display = "none";

        }

      }

      

      function clear() {

        document.getElementById("leftContainer").innerHTML = '';

      }

      

      function selectItem() {

        // Code to print the name of the selected item on the right side of the page

        var selectedItem = event.target.innerHTML;

        document.getElementById("rightContainer").innerHTML = selectedItem;

      }

    

      function editItem() {

        var item = event.target;

        var value = item.innerHTML;

        var index = Array.from(item.parentNode.children).indexOf(item);

      

        item.innerHTML = "<input type='text' value='" + value + "'>";

      

        var input = item.children[0];

        input.addEventListener("keydown", function(e) {

          if (e.key === "Enter") {

            item.innerHTML = input.value;

            if (index >= 0 && index < data.length) {

              data[index] = input.value;

            }

          }

        });

      }

      

      document.getElementById("update").addEventListener("click", update);

      document.getElementById("expandAll").addEventListener("click", expandAll);

      document.getElementById("collapseAll").addEventListener("click", collapseAll);

      document.getElementById("clear").addEventListener("click", clear);

      document.getElementById("fileName").addEventListener("change", update);

      

      var leftContainer = document.getElementById("leftContainer");

      leftContainer.addEventListener("click", selectItem);

      leftContainer.addEventListener("dblclick", editItem);

      

    } );

 

 

 

Third Upgrade

 

This is done by chatGPT with GPT-4 model on Mar 15. I tried to implement this functionality with previous version (GPT 3.5) and spent hours of troubleshooting but failed, but with GPT 4  I got this working at single shot.

 

First I asked chatGPT(GPT 4) using the same prompt as in the First Version, and then did First Upgrade and Second Upgrade. And then requested the following.

 

Rewrite the function "function displayTree" to do followings :

i) when "image/arrow_up.png" for a specific node is clicked, collapse the node

ii) when "image/arrow_down.png" for a specific node is clicked, explode the node

 

Surprisingly every step is done at the first trial without any debugging effort.

Usage : You can click on a small triangle at the left of each node to expand and collapse it.

 

 

 

 

Fourth Upgrade

 

 

Now I noticed one thing that was not implemented as I extended. When I click on [Collapse All] button, it collapses all the way up to root node. But this was not what I intended. I wanted to collapse only up to the nearest parent. So I requested as follows.

 

can you rewrite 'function displayTree' to do the following things when 'Collapse All' button ?

i) Collapse only upto the nearest parent branch. Not all the way up to root.

 

This also was done only in two trial (I tried the same thing with chatGPT(GPT 3.5), but it never worked as I wanted even with many back and forth).

 

 

 

Fifth Upgrade

 

 

One last issue that I found was about the editing functionality. When I double clicked on a node to edit the value of the node there were two problems.

  • The textbox for the editing showed 'key' part of the node, not the 'value' part
  • When I changed the text and pressed enter key, nothing happened

I put a follow-up request as follows.

When I doubleclick on the last node, it shows textbox showing the key value. It should be changed as follows.

i) The text box should shaw 'value' part, not key part.

ii) If I edit the text and press enter key, the value part should be changed as I edited.

 

and this problem was resolved in single shot.

 

I think I got a pretty good final result as a viewer and decided to stop here for writing the viewer. Following is the final version of the code that I got.

 

JasonViewer_GPT4_r3.html

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" href="JsonViewer_GPT4_r1.css">

    <title>JSON Viewer</title>

</head>

<body>

    <div id="menu-bar">

        <input type="file" id="fileName">

        <button id="update">Update</button>

        <button id="expandAll">Expand All</button>

        <button id="collapseAll">Collapse All</button>

        <button id="clear">Clear</button>

    </div>

    <div id="main-container">

        <div id="left-container"></div>

        <div id="right-container"></div>

    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

    <script src="JsonViewer_GPT4_r3.js"></script>

</body>

</html>

 

JsonViewer_GPT4_r1.css

body {

    font-family: Arial, sans-serif;

}

 

#menu-bar {

    display: flex;

    align-items: center;

    background-color: #f0f0f0;

    padding: 10px;

}

 

#main-container {

    display: flex;

    height: calc(100vh - 50px);

}

 

#left-container,

#right-container {

    flex: 1;

    padding: 10px;

    border: 1px solid #ccc;

    overflow: auto;

}

 

#right-container {

    border-left: none;

}

 

 

JsonViewer_GPT4_r3.js

$(document).ready(function () {

    const fileName = $("#fileName");

    const leftContainer = $("#left-container");

    const rightContainer = $("#right-container");

 

    fileName.on("change", handleFileSelect);

    $("#update").on("click", handleUpdate);

    $("#expandAll").on("click", handleExpandAll);

    $("#collapseAll").on("click", handleCollapseAll);

    $("#clear").on("click", handleClear);

 

    function handleFileSelect() {

        const file = fileName[0].files[0];

        fileName.val(file.name);

    }

 

    function handleUpdate() {

        const file = fileName[0].files[0];

        if (file) {

            const reader = new FileReader();

            reader.onload = function (e) {

                try {

                    const json = JSON.parse(e.target.result);

                    leftContainer.empty();

                    displayTree(json, leftContainer);

                } catch (err) {

                    console.error("Invalid JSON format.");

                }

            };

            reader.readAsText(file);

        }

    }

 

    function displayTree(json, container, level = 0) {

        if (typeof json !== "object" || json === null) {

            return;

        }

        for (const key in json) {

            const value = json[key];

            let displayValue = '';

            let isExpandable = false;

    

            if (typeof value !== 'object' || value === null) {

                displayValue = ': ' + JSON.stringify(value);

            } else {

                isExpandable = true;

            }

    

            const arrow = isExpandable ? '<img class="arrow" src="image/arrow_down.png">' : '';

            const item = $(`<div class="tree-item" data-level="${level}" style="padding-left: ${level * 20}px">${arrow}${key}${displayValue}</div>`);

            container.append(item);

            item.on("click", function (e) {

                if ($(e.target).hasClass("arrow")) {

                    const arrowImage = $(e.target);

                    const isCollapsed = arrowImage.attr("src") === "image/arrow_down.png";

    

                    if (isCollapsed) {

                        arrowImage.attr("src", "image/arrow_up.png");

                        item.nextUntil(".tree-item[data-level='" + level + "']").hide();

                    } else {

                        arrowImage.attr("src", "image/arrow_down.png");

                        item.nextUntil(".tree-item[data-level='" + level + "']").show();

                    }

                } else {

                    rightContainer.text(`${key}: ${JSON.stringify(value)}`);

                }

            });

            item.on("dblclick", function (e) {

                if ($(e.target).hasClass("arrow")) {

                    return;

                }

                const isLastNode = typeof value !== 'object' || value === null;

                if (isLastNode) {

                    const input = $("<input type='text' value='" + JSON.stringify(value).slice(1, -1) + "'>");

                    input.on("keydown", function (event) {

                        if (event.key === "Enter") {

                            const newValue = input.val();

                            displayValue = ': ' + newValue;

                            item.text(key + displayValue);

                            input.detach();

                        }

                    });

                    item.append(input);

                    input.focus();

                } else {

                    const input = $("<input type='text' value='" + key + "'>");

                    input.on("blur", function () {

                        const newValue = input.val();

                        item.text(newValue + displayValue);

                        item.append(input.detach());

                    });

                    item.append(input);

                    input.focus();

                }

            });

            if (isExpandable) {

                displayTree(value, container, level + 1);

            }

        }

    }

    

    

    function handleCollapseAll() {

        $(".tree-item").each(function () {

            const level = parseInt($(this).attr("data-level"));

            const nextItemLevel = parseInt($(this).next().attr("data-level"));

    

            if (level > 0 && (isNaN(nextItemLevel) || nextItemLevel <= level)) {

                $(this).hide();

                const arrow = $(this).find(".arrow");

                if (arrow.length > 0) {

                    arrow.attr("src", "image/arrow_down.png");

                }

            }

        });

    }

    

    

    

 

    function handleExpandAll() {

        $(".tree-item").css("display", "block");

    }

 

 

    function handleClear() {

        leftContainer.empty();

        rightContainer.empty();

    }

});