I have a file/folder tree structure as follows:
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" />
<ul id="myTreeSelector">
<li>
<span class="caret folder-selector"><i class="fa fa-folder"></i> first_folder</span>
<ul class="nested">
<li class="file"><i class="fa fa-file"></i> app1.dat</li>
<li class="file"><i class="fa fa-file"></i> app2.dat</li>
<li>
<span class="caret folder-selector"><i class="fa fa-folder"></i> second_folder</span>
<ul class="nested">
<li class="file"><i class="fa fa-file"></i> ret.dat</li>
<li><span class="caret folder-selector"><i class="fa fa-folder"></i> third_folder</span></li>
</ul>
</li>
</ul>
</li>
</ul>
Now, I want to create dynamically that folder structure for a given array with files and folders. This means if I input this:
var arrayOfFiles = [
"/first_folder/app_01.dat",
"/first_folder/app_02.dat",
"/first_folder/second_folder/ret_01.dat",
"/first_folder/second_folder/ret_02.dat",
"/first_folder/second_folder/third_folder"
];
I want to output this output:
This is a solution taken from another post I made some days ago:
HTML:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" />
<ul id="myTreeSelector">
</ul>
CSS:
ul {
list-style-type: none;
}
i {
margin-right: 5px;
}
JS:
var arrayOfFiles = [
"/first_folder/app_01.dat",
"/first_folder/app_02.dat",
"/first_folder/second_folder/ret_01.dat",
"/first_folder/second_folder/ret_02.dat",
"/first_folder/second_folder/third_folder"
];
var wrap = document.getElementById("myTreeSelector");
var lines = "----";
var res = {};
function makeObj(obj) {
var splme = obj.split('/').slice(1);
var f = res;
for (i = 0; i < splme.length; i++) {
obName = { name: splme[i] };
f = f[splme[i]] = f[splme[i]] || obName;
}
}
arrayOfFiles.map(makeObj);
var tmp = [];
function prsObj(x, y) {
for (var k in x) {
if (typeof x[k] === 'object' && x[k] !== null) {
prsObj(x[k], y);
}
else if (x.hasOwnProperty(k)) {
y(x[k]);
}
tmp.push(lines);
}
};
prsObj(res, function (ftmp) { tmp.push(ftmp); });
var redy = [];
for (var i = 0; i < tmp.length - 1; i++) {
if (tmp[i] !== lines) {
redy.push(tmp[i]);
}
else if (tmp[i] === lines && tmp[i + 1] === lines) {
redy.push("file");
i++;
}
else if (tmp[i] === lines && tmp[i + 1] !== lines) {
redy.push("folder");
} else { }
}
redy.pop();
var text = '';
var x = 0;
var y = 0;
for (var i = 0; i < redy.length; i++) {
if (y === 1) {
var text = document.createTextNode(redy[i]);
addThis(0);
x = 0;
y = 0;
}
else if (redy[i + 1] === "file" && redy[i + 2] === "file") {
if (redy[i].indexOf(".") > -1) {
var text = document.createTextNode(redy[i]);
addThis(1);
i = i + 2;
x = 0;
y = 1;
} else {
var text = document.createTextNode(redy[i]);
addThis(2);
i = i + 2;
x = 0;
y = 1;
}
}
else if (redy[i + 1] === "folder" && i === 0) {
var text = document.createTextNode(redy[i]);
addThis(0);
x++;
y = 0;
}
else if (redy[i + 1] === "folder" && x >= 1 || redy[i + 1] === "folder") {
var text = document.createTextNode(redy[i]);
addThis(2);
x++;
y = 0;
}
else if (redy[i + 1] === "file") {
if (redy[i] === '') { continue; }
else if (redy[i].indexOf(".") > -1) {
var text = document.createTextNode(redy[i]);
addThis(1);
y = 0;
} else {
var text = document.createTextNode(redy[i]);
addThis(2);
y = 0;
}
}
else { }
}
function addThis(x) {
if (x === 0) {
var li = document.createElement("li");
var span = document.createElement("span");
span.setAttribute("class", "caret folder-selector");
var iel = document.createElement("i");
iel.setAttribute("class", "fa fa-folder");
var ul = document.createElement("ul");
ul.setAttribute("class", "nested");
span.appendChild(iel);
span.appendChild(text);
li.appendChild(span);
wrap.appendChild(li);
li.appendChild(ul);
}
if (x === 1) {
var get = wrap.getElementsByClassName("nested");
var ul = get[get.length - 1];
var iel = document.createElement("i");
iel.setAttribute("class", "fa fa-file");
var li = document.createElement("li");
li.setAttribute("class", "file");
ul.appendChild(li);
li.appendChild(iel);
li.appendChild(text);
}
if (x === 2) {
var get = wrap.getElementsByClassName("nested");
var ul = get[get.length - 1];
var li = document.createElement("li");
var span = document.createElement("span");
span.setAttribute("class", "caret folder-selector");
var iel = document.createElement("i");
iel.setAttribute("class", "fa fa-folder");
ul.appendChild(li);
li.appendChild(span);
span.appendChild(iel);
span.appendChild(text);
var get = wrap.querySelectorAll("li")
var li = get[get.length - 1];
var ul = document.createElement("ul");
ul.setAttribute("class", "nested");
li.appendChild(ul);
}
}
That works for that example, but when I input other type of array, it doesn't create the tree correctly (see the jsfiddle at the bottom):
// Doesn't work correctly with this inputs
var arrayOfFiles = [
"/myfiles/",
"/myfiles/Backup/",
"/myfiles/Backup/one/",
"/myfiles/Backup/one/test1.dat",
"/myfiles/Backup/two/",
"/myfiles/Backup/two/test2.dat"
]
var arrayOfFiles = [
"/first_folder/",
"/first_folder/second_folder/",
"/first_folder/second_folder/asd/",
"/first_folder/third/"
];
I'm not a JS expert so it has been a big nightmare for me to try to fix this, and make it work for any input. I'm open to any other solution that works, it doesn't have to be the code of above.
Here's the Jsfiddle: https://jsfiddle.net/j168f0qy/
Thanks in advance.
1 Answer 1
The keyword for this is trie
An approach can be to simply manipulate a tree and then print it back recursively
const arrayOfFiles = [
"/myfiles/",
"/myfiles/Backup/",
"/myfiles/Backup/one/",
"/myfiles/Backup/one/test1.dat",
"/myfiles/Backup/two/",
"/myfiles/Backup/two/test2.dat",
"/myfiles/Backup/three/",
"/myfiles/Backup/four",
]
// build the tree
const root = {}
arrayOfFiles.forEach(file => {
let anchor = root
file.split('/').forEach(token => {
if (token === '') {
Object.defineProperty(anchor, 'isFolder', {
value: true,
enumerable: false
})
return
}
anchor[token] = anchor[token] || {}
anchor = anchor[token]
})
})
// plot the tree
const toHtml = tree => {
if (Object.keys(tree).length === 0) return ''
return [
'<ul>',
Object.keys(tree).map(key => {
const icon = Object.keys(tree[key]).length === 0 && !tree[key].isFolder ? 'file' : 'folder'
return `<li class="${icon}">${key}${toHtml(tree[key])}</li>`
}).join('\n'),
'</ul>'
].join('\n')
}
document.getElementById('gro').innerHTML = toHtml(root)
.folder{color: blue}
.file {color: red}
<div id="gro"></div>
4 Comments
<span> and folder and file classes. If i'm not wrong, with your solution is imposible to find out which is a folder and which is a file.ret_01.dat is a file and that third_folder is a folder?