JavaScript Interview Question 1 ( Directory Mapping ๐ )
How to create a directory structure in HTML, based on the given directory object using JavaScript.
Table of contents
Problem
We are given an object representing a directory structure, where each directory can contain files and subdirectories in a nested fashion. The object contains fields such as name
, isFolder
(to check if it is a file or folder), and children
(an array representing the nested directories and files). The task is to create an HTML representation of the directory structure and append it to the body element.
const directory = {
name: 'project',
isFolder: true,
children: [
{
name: "public",
isFolder: true,
children: [
{
name: "index.html",
isFolder: false,
},
{
name: "favicon.ico",
isFolder: false,
},
]
},
{
name: "src",
isFolder: true,
children: [
{
name: "index.css",
isFolder: false,
},
{
name: "index.js",
isFolder: false,
},
{
name: "scripts",
isFolder: true,
children: [
{
name: "about.js",
isFolder: false,
},
{
name: "home.js",
isFolder: false,
},
]
}
]
}
]
}
Representing the directory
We can represent the directory using the HTML <details>
and <summary>
elements allow us to create an expandable directory-like structure in the HTML representation. The <details>
element serves as the container for each directory, and the <summary>
element acts as the label or name of the directory. By utilizing these elements, we can achieve a collapsible and expandable directory structure in HTML.
<details>
<summary>
Click This to expand
</summary>
<p>
The details of the clicked summary is visible.
</p>
</details>
Approach
First Create a function for mapping the directory and a helper function inside it to perform recursion for mapping the nested directory. The directoryMap
function takes two arguments first the directory
object and the DOM entry
node of the HTML in which we have to put the directory mapping.
const directoryMap = (directory, entry) => {
const helper = (directory) => {
}
const domElement = helper(directory);
// if the helper function returns null skip appending
if (domElement !== null) {
entry.appendChild(domElement);
}
}
const body = document.querySelector('body');
directoryMap(directory, body);
Now Let's complete our helper function, The helper
function is responsible for mapping a directory object to its corresponding HTML representation. It takes a directory
object as input and performs recursion to handle nested directories and files.
const helper = (directory) => {
const { name, isFolder, children } = directory;
if (isFolder) {
var el = document.createElement('details');
} else {
var el = document.createElement('p');
}
}
The helper
function begins by extracting the name
, isFolder
, and children
properties from the directory
object. It then creates the appropriate HTML element based on whether it is a folder or a file. If it is a folder, it creates <details>
element, otherwise, it creates <p>
element, since there is no need to expand or a file doesn't have nesting.
const fname = document.createElement('summary');
fname.innerText = name;
Next, it creates <summary>
element and sets its text content to the directory's name. The <summary>
element serves as the label for the directory, then append the summary to the el
which is the parent of the current directory.
If the directory is a folder, it proceeds to create <p>
element to hold the nested directories and files. It iterates over the children
array and recursively calls the helper
function for each child directory or file. The resulting HTML elements are appended to a fragment
, which is then appended to the <p>
element.
Finally, the <p>
element (containing nested directories/files, if applicable) is appended to the main element (<details>
or <p>
) representing the current directory. The main element is then returned.
const helper = (directory) => {
const { name, isFolder, children } = directory;
if (isFolder) {
var el = document.createElement('details');
} else {
var el = document.createElement('p');
}
const fname = document.createElement('summary');
// applying file/folder name in summary element
fname.innerText = name;
// adding summary element to <details>/<p>
el.appendChild(fname);
// creating p element for holding directory children
const desc = document.createElement('p');
/* if the directory is a folder the iterate over its children
* array and recursively calls the helper function for
* each child directory or file.
*/
if (isFolder) {
// Fragment is used here for performance optimization.
for (let child of children) {
fragment.appendChild(helper(child));
}
desc.appendChild(fragment);
}
el.appendChild(desc);
return el;
}
The benefit of creating a document fragment and appending child elements to it before appending them to the main element (<details> or <p>) lies in performance optimization since we aren't manipulating dom for each child and its nesting, we are putting all the nesting of a child in a nesting and once done we are putting the nesting in <p> element.
/**
* @param {Object} directory
* @param {HTMLElement} entry
*/
const directoryMap = (directory, entry) => {
const helper = (directory) => {
const { name, isFolder, children } = directory;
// if the directory is folder we create <summary> element else <p>
if (isFolder) {
var el = document.createElement('details');
} else {
var el = document.createElement('p');
}
const fname = document.createElement('summary');
// applying file/folder name in summary element
fname.innerText = name;
// adding summary element to <details>/<p>
el.appendChild(fname);
// creating p element for holding directory children
const desc = document.createElement('p');
/* if the directory is a folder the iterate over its children
* array and recursively calls the helper function for
* each child directory or file.
*/
if (isFolder) {
// Fragment is used here for performance optimization.
for (let child of children) {
fragment.appendChild(helper(child));
}
desc.appendChild(fragment);
}
// adding the children to the respective parent directory
el.appendChild(desc);
// returning the current directory node
return el;
}
// getting the directory dom structure
const domElement = helper(directory);
// putting the directory dom structure in entry elemrnt if it is not null.
if (domElement !== null) {
entry.appendChild(domElement);
}
}
Back in the directoryMap
function, the helper
function is called with the directory
object and the resulting HTML element is stored in domElement
. If domElement
is not null, it means there is a valid HTML representation of the directory, so it is appended to the entry
element (DOM node) provided as an argument.
By executing directoryMap(directory, body)
, the directory mapping is generated and appended to the <body>
element of the HTML document.
Full Code
<!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>Directory Mapping</title>
<style>
* {
margin: 0;
padding: 0;
}
p {
margin-left: 10px;
}
details {
padding: 3px;
}
</style>
</head>
<body>
<script>
const directory = {
name: 'project',
isFolder: true,
children: [
{
name: "public",
isFolder: true,
children: [
{
name: "index.html",
isFolder: false,
},
{
name: "favicon.ico",
isFolder: false,
},
]
},
{
name: "src",
isFolder: true,
children: [
{
name: "index.css",
isFolder: false,
},
{
name: "index.js",
isFolder: false,
},
{
name: "scripts",
isFolder: true,
children: [
{
name: "about.js",
isFolder: false,
},
{
name: "home.js",
isFolder: false,
},
]
}
]
}
]
}
/**
*
* @param {Object} directory
* @param {HTMLElement} entry
*/
const directoryMap = (directory, entry) => {
const helper = (directory) => {
const { name, isFolder, children } = directory;
if (isFolder) {
var el = document.createElement('details');
} else {
var el = document.createElement('p');
}
const fname = document.createElement('summary');
fname.innerText = name;
el.appendChild(fname);
const desc = document.createElement('p');
if (isFolder) {
const fragment = document.createDocumentFragment();
for (let child of children) {
fragment.appendChild(helper(child));
}
desc.appendChild(fragment);
}
el.appendChild(desc);
return el;
}
const domElement = helper(directory);
if (domElement !== null) {
entry.appendChild(domElement);
}
}
const body = document.querySelector('body');
directoryMap(directory, body);
</script>
</body>
</html>
Output
Conclusion
In this blog post, we explored a common JavaScript interview question related to directory mapping. We discussed a method for creating an HTML representation of a directory structure using <details>
and <summary>
elements, which allows for an extendible and collapsible directory structure-like structure.
If you enjoyed this article, please leave a like. I highly appreciate any feedback from you and any suggestions are welcome too!
Follow me on Twitter.