Expandable dynamic nested list with objects array - javascript

Im struggling with expandable nested array list anyone keen to help me out
https://jsfiddle.net/AccordEuro06/2otnxb9c/3/
const webfolder = [{
name: 'Webpage',
type: 'folder',
children: [{
name: 'php',
type: 'folder',
children: [{
name: 'header.php',
type: 'file'
},
{
name: 'footer.php',
type: 'file'
},
]
},
{
name: 'css',
type: 'folder',
children: [{
name: 'images',
type: 'folder',
children: [{
name: 'logos',
type: 'folder',
children: [{
name: 'Colored.svg',
type: 'file'
},
{
name: 'Monochrome.svg',
type: 'file'
},
]
},
{
name: 'gallery',
type: 'folder',
children: [{
name: 'image1.jpg',
type: 'file'
},
{
name: 'image2.png',
type: 'file'
},
{
name: 'image3.gif',
type: 'file'
},
{
name: 'image4.jpeg',
type: 'file'
},
{
name: 'image5.jpg',
type: 'file'
},
]
},
{
name: 'about-us.jpg',
type: 'file'
},
],
},
{
name: 'style.css',
type: 'file'
},
]
},
{
name: 'javascipt',
type: 'folder',
children: [{
name: 'script.js',
type: 'file'
},
{
name: 'script2.js',
type: 'file'
},
{
name: 'ajax.js',
type: 'file'
},
{
name: 'jquery.js',
type: 'file'
},
]
},
{
name: 'index.html',
type: 'file'
}
]
}];
function listHtml(children) {
return '<ul>' + children.map(node =>
'<li>' + node.name +
(node.type === 'file' ? '' : listHtml(node.children)) +
'</li>').join('\n') +
'</ul>';
}
document.getElementById('expandableTree').innerHTML += listHtml(webfolder);
I understand that hide/show on click should be done with css but i ran out of ideas how to add proper classes to branches and leafs

You can use the HTML5 <details> elements:
function listHtml(children) {
return '<ul>' + children.map(node =>
`<li>
${node.type === 'file'
? node.name
: `<details>
<summary>${node.name}</summary>
${listHtml(node.children)}
</details>`
}
</li>`
).join('\n') + '</ul>';
}
document.getElementById('expandableTree').innerHTML += listHtml(webfolder);
Try it on CodePen

Related

Array data is getting replaced inside forEach nested with map in JavaScript/TypeScript

Why 1st iteration data is getting replaced in 2nd iteration?
Is there any other simpler method in ES6 to achieve this?
a = [
{ name: 'NameOne', weekName: 'WeekOne' },
{ name: 'NameTwo', weekName: 'WeekTwo' },
];
b = [
{ id: 'Name', type: 'text', data: '' },
{ id: 'Week', type: 'text', data: '' },
];
c = [];
showOutput() {
this.a.forEach((element) => {
this.b.map((item) => {
if (item.id == 'Name') {
item.data = element.name;
}
if (item.id == 'Week') {
item.data = element.weekName;
}
this.c.push(item);
console.log('c', this.c);
});
});
}
Current Output :
[{ id: 'Name', type: 'text', data: 'NameTwo' },
{ id: 'Week', type: 'text', data: 'WeekTwo' },
{ id: 'Name', type: 'text', data: 'NameTwo' },
{ id: 'Week', type: 'text', data: 'WeekTwo' }]
Desired Output:
[{ id: 'Name', type: 'text', data: 'NameOne' },
{ id: 'Week', type: 'text', data: 'WeekOne' },
{ id: 'Name', type: 'text', data: 'NameTwo' },
{ id: 'Week', type: 'text', data: 'WeekTwo' }]
Problem with your code is that this.c.push(item); here the same object is getting referenced so in 2nd iteration it's changing the data that modified by 1st iteration. In order to solve this, you will have to clone the object (dereference somehow)
I have used c.push(Object.assign({}, item)); or you can use c.push(JSON.parse(JSON.stringify(item))); or any other way to clone the object before pushing into array (c in your case)
Note: This is just to point out the root cause of the issue, and it may not be the perfect solution for your scenario.
e.g.
a = [
{ name: 'NameOne', weekName: 'WeekOne' },
{ name: 'NameTwo', weekName: 'WeekTwo' },
];
b = [
{ id: 'Name', type: 'text', data: '' },
{ id: 'Week', type: 'text', data: '' },
];
c = [];
function showOutput() {
a.forEach((element) => {
b.map((item) => {
if (item.id == 'Name') {
item.data = element.name;
}
if (item.id == 'Week') {
item.data = element.weekName;
}
c.push(Object.assign({}, item)); // clone object
});
});
}
showOutput();
console.log('c', c);
For more information: https://javascript.info/object-copy

React.Js. Mapping in nested array to update state

In React.js I have array of object:
arr = [
{
type: 'folder',
name: 'src',
id: '1',
files: [
{
type: 'folder',
name: 'name 1',
id: '2',
files: [
{ type: 'file', name: 'JSFile.js', id: '3' },
],
},
{
type: 'folder',
name: 'name 2 ',
id: '6',
files: [
{ type: 'file', name: 'File.js', id: '7' },
{ type: 'file', name: 'JSXfile.jsx', id: '14' },
type: 'folder',
name: 'name 1',
id: '6',
files: [
{ type: 'file', name: 'HTMLfile.html', id: '5' },
],
},
],
},
],
},
]
I have an array of objects, and I want to update this array with some object, for example something like this:
const onAddFile = () => {
const newFile = {
type: 'file',
name: 'SXfile',
id: "100,
}
const folderToPush = files: (1) [{…}],
id: "6",
name: "name 1 ",
type: "folder" <---this is folder which I have selected via click, which I have id of folder and array of files that I have push newFile. and update state instantly.
folderToPush.files.push(newFile)
}
expected output is something like this:
arr = [
{
type: 'folder',
name: 'src',
id: '1',
files: [
{
type: 'folder',
name: 'name 1',
id: '2',
files: [
{ type: 'file', name: 'JSFile.js', id: '3' },
],
},
{
type: 'folder',
name: 'name 2 ',
id: '6',
files: [
{ type: 'file', name: 'File.js', id: '7' },
{ type: 'file', name: 'JSXfile.jsx', id: '14' },
type: 'folder',
name: 'name 1',
id: '6',
files: [
{ type: 'file', name: 'HTMLfile.html', id: '5' },
{ type: 'file', name: 'SXfile.jsx', id: '100' }, <--- here is pushed object
],
},
],
},
],
},
]
I know it have to be some recursive function to map through whole array, but I have no idea how to implement this. Is there a way to make it, with useState only to see changes instantly?

Core UI Master menus expand/collapse not working smoothly for some items

Master menus not working smoothly. Please check below observations:
Scenario-1
1.Click on Project menu
2.Select 1st sub-menu, respective page opens up and menu remains open
3.Select 2nd sub-menu, respective page opens up and menu gets closed
4.Same happens with 3rd menu too
It should be: The way menu is working on selecting 1st option, same has to be implemented for other sub-menu’s.
Scenario-2
Click on Project menu
It expands and displays all of its options
Now Click on Client menu
It expands and displays all of its options but project menu still remains expanded
It should be: As soon as another menu is clicked, all previously expanded menu’s should get collapsed.
It is working fine at https://coreui.io/angular/demo/free/2.11.1/#/dashboard
default-layout\default-layout.component.html
<app-sidebar #appSidebar [fixed]="true" [display]="'lg'" [minimized]="sidebarMinimized" (minimizedChange)="toggleMinimize($event)" class="pb-1">
<div class="sidebar-brand d-md-none d-sm-none d-lg-block"><img src="assets/img/brand/logo.png"></div>
<app-sidebar-nav [navItems]="navItems" [perfectScrollbar] [disabled]="appSidebar.minimized" class="pt-1"></app-sidebar-nav>
</app-sidebar>
sidebar.service.ts
import { Injectable } from '#angular/core';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class SidebarService {
constructor(private router : Router) { }
menuItems: any = [];
makeNavItemModuleBased(module, role) {
let url = (role == 'ROLE_ADMIN') ? '/dashboard/admin' : (role == 'ROLE_MANAGER') ? '/dashboard/manager' : '/dashboard/employee';
let manuItem: any;
if (module == 'USER_MANAGEMENT') {
manuItem = {
name: 'User',
url: '/user/existing-users',
};
}
if (module == 'REPORT_MANAGEMENT') {
manuItem = {
name: 'Reports',
url: '/reports',
// icon: 'cil-building',
children: [
{
name: 'Project',
url: '/reports/project-progress-report',
// icon: 'icon-puzzle',
class: 'nav-link',
}
]
};
}
if (module == 'EMPLOYEE_MANAGEMENT') {
manuItem = {
name: 'Employee',
url: '/employee',
// icon: 'cil-group',
children: [
{
name: 'Employees',
url: '/employee/existing-employees',
// icon: 'cil-library',
class: 'nav-link',
},
{
name: 'Employee Allocation',
url: '/project/allocation',
// icon: 'cil-library',
class: 'nav-link',
},
]
};
}
if (module == 'PROJECT_MANAGEMENT') {
manuItem = {
name: 'Project',
url: '/project',
// icon: 'icon-menu',
children: [
{
name: 'Projects',
url: '/project/existing-projects',
// icon: 'cil-library',
class: 'nav-link',
},
{
name: 'Clients',
url: '/client/existing-clients',
// icon: 'cil-library',
class: 'nav-link',
}
]
};
}
return manuItem;
}
makeNavItem(role: any) {
let emClass ='nav-link';
if(this.router.url == '/employee/add-new-employee' || this.router.url == '/employee/edit-employee/:employeeId' )
{
console.log('url',this.router.url);
emClass ='nav-link active';
}
let url = (role == 'ROLE_ADMIN') ? '/dashboard/admin' : (role == 'ROLE_MANAGER') ? '/dashboard/manager' : '/dashboard/employee';
this.menuItems.push(
{
name: 'Dashboard',
url: url,
type: 'dashboard',
attributes: { hidden: false },
title: false,
},
{
title: true,
name: 'Resource Management',
type: 'resource',
attributes: { hidden: false }
},
{
name: 'Human Resource',
attributes: { hidden: true },
type: 'EMPLOYEE_MANAGEMENT',
url: '/employee/existing-employees',
title: false,
},
{
name: 'Clients',
attributes: { hidden: true },
type: 'PROJECT_MANAGEMENT',
url: '/client/existing-clients',
title: false,
},
{
name: 'Projects',
attributes: { hidden: true },
type: 'PROJECT_MANAGEMENT',
url: '/project/existing-projects',
title: false,
},
{
name: 'Request',
attributes: { hidden: true },
type: 'PROJECT_MANAGEMENT',
url: '/request',
title: false,
},
{
name: 'Assets',
attributes: { hidden: true },
type: 'PROJECT_MANAGEMENT',
url: '/assets',
title: false,
},
{
name: 'Reports',
attributes: { hidden: true },
type: 'REPORT_MANAGEMENT',
url: '/reports',
title: false,
children: [
{
name: 'Project',
url: '/reports/project-progress-report',
class: 'nav-link',
}
]
},
{
title: true,
name: "ADMIN",
attributes: { hidden: true },
url: '/admin',
type: 'ROLE_ADMIN',
},
{
name: 'General',
attributes: { hidden: true },
type: 'ROLE_ADMIN',
url: '/department',
title: false,
children: [
{
name: 'Department',
url: '/department/existing-departments',
class: 'nav-link',
},
]
},
{
name: 'Client',
attributes: { hidden: true },
type: 'ROLE_ADMIN',
url: '/client',
title: false,
children: [
{
name: 'Lead Type',
url: '/client/existing-lead-type',
class: 'nav-link',
},
{
name: 'Industry Type',
url: '/client/existing-industry-type',
class: 'nav-link',
},
]
},
{
name: 'Project',
attributes: { hidden: true },
type: 'ROLE_ADMIN',
url: '/project',
title: false,
children: [
{
name: 'Project Status',
url: '/project/existing-projects-status',
class: 'nav-link',
},
{
name: 'Technology',
url: '/technology/existing-technologies',
class: 'nav-link',
},
{
name: 'Skill',
url: '/skill/existing-skills',
class: 'nav-link',
},
]
},
{
name: 'Employee',
attributes: { hidden: true },
type: 'ROLE_ADMIN',
url: '/employee',
title: false,
children: [
{
name: 'Designation',
url: '/designation/existing-designations',
class: 'nav-link',
},
{
name: 'Employee Allocation Percentage',
url: '/employee/existing-employees-allocation-percentage',
class: 'nav-link',
},
{
name: 'Allocation Type',
url: '/project/existing-allocation-type',
class: 'nav-link',
},
]
},
{
name: 'User',
type: 'ROLE_ADMIN',
url: '/user/existing-users',
title: false,
},
{
name: 'Asset',
attributes: { hidden: true },
type: 'ROLE_ADMIN',
url: '/asset',
title: false,
children: [
{
name: 'Asset',
url: '/asset/existing-assets',
class: 'nav-link',
},
]
},
{
name: 'Security',
attributes: { hidden: true },
type: 'ROLE_ADMIN',
url: '/settings',
title: false,
children: [
{
name: 'Settings',
url: '/settings',
class: 'nav-link',
},
{
name: 'Roles & Permissions',
url: '/role/existing-roles',
class: 'nav-link',
},
{
name: 'Roles Module',
url: '/role/existing-module',
class: 'nav-link',
},
]
},
)
return this.menuItems;
}
makeItemShow(menuItem: any, modules: any, role: any) {
console.log('role', role);
let menus = [];
menuItem.forEach(menu => {
if (menu.type == 'dashboard' || menu.type == 'resource') {
let index = menus.findIndex(p => p.name == menu.name)
if (index === -1) {
menus.push({
name: menu.name,
attributes: { hidden: null },
url: menu.url,
type: menu.type,
title: menu.title,
children: menu.children
},
{ divider: true }
)
}
}
modules.forEach(async (element, index) => {
if (menu.type == element) {
let index = menus.findIndex(p => p.name == menu.name)
if (index === -1) {
menus.push({
name: menu.name,
attributes: { hidden: null },
url: menu.url,
type: menu.type,
title: menu.title,
children: menu.children
},
{ divider: true }
)
}
}
});
if (menu.type == role) {
let index = menus.findIndex(p => p.name == menu.name)
if (index === -1) {
menus.push({
name: menu.name,
attributes: { hidden: null },
url: menu.url,
type: menu.type,
title: menu.title,
children: menu.children
},
{ divider: true }
)
}
}
})
console.log('menus', menus);
return menus;
}
}
It only happening with some menus not on all and I am an UI developer so unable to figure out the issue. Any help would be appreciated Thanks in adnvance :)

creating a nested unsorted list from a javascript array

I'm struggling to figure out how to create a nested unsorted list from a predefined javascript array. The array looks like this
var directory = [
{ type: 'file', name: 'file1.txt' },
{ type: 'file', name: 'file2.txt' },
{
type: 'directory',
name: 'HTML Files',
files: [
{ type: 'file', name: 'file1.html' },
{ type: 'file', name: 'file2.html' }
]
},
{ type: 'file', name: 'file3.txt' },
{
type: 'directory',
name: 'JavaScript Files',
files: [
{ type: 'file', name: 'file1.js' },
{ type: 'file', name: 'file2.js' },
{ type: 'file', name: 'file3.js' }
]
}
];
And the output I'm reaching for is
<ul>
<li>file1</li>
<li>file2</li>
<li>HTML files:
<ul>
<li>file1</li>
<li>file2</li>
</ul>
</li>
<li>
file3
</li>
<li>JavaScript Files:
<ul>
<li>file1</li>
<li>file2</li>
<li>file3</li>
</ul>
</ul>
You can use a few forEach's like below:
var list = [];
var listItem;
var directory = [
{ type: 'file', name: 'file1.txt' },
{ type: 'file', name: 'file2.txt' },
{
type: 'directory',
name: 'HTML Files',
files: [
{ type: 'file', name: 'file1.html' },
{ type: 'file', name: 'file2.html' }
]
},
{ type: 'file', name: 'file3.txt' },
{
type: 'directory',
name: 'JavaScript Files',
files: [
{ type: 'file', name: 'file1.js' },
{ type: 'file', name: 'file2.js' },
{ type: 'file', name: 'file3.js' }
]
}
];
directory.forEach(element => {
if (element.type == "file") {
listItem = "<li>" + element.name.match(/[^.]*/) + "</li>";
list.push(listItem);
}
else if (element.type == "directory") {
listItem = "<li>" + element.name + "<ul>";
element.files.forEach(e => {
listItem += "<li>" + e.name.match(/[^.]*/) + "</li>";
});
listItem += "</ul></li>"
list.push(listItem);
}
});
document.getElementById("list").innerHTML = list.join('');
<h2>Expected Result</h2>
<ul style="background: orange;">
<li>file1</li>
<li>file2</li>
<li>HTML files:
<ul>
<li>file1</li>
<li>file2</li>
</ul>
</li>
<li>
file3
</li>
<li>JavaScript Files:
<ul>
<li>file1</li>
<li>file2</li>
<li>file3</li>
</ul>
</ul>
<h2>Output</h2>
<ul id="list" style="background: yellow"></ul>
You can create a recursive function to walk through the directory.
<!doctype html>
<html>
<body>
</body>
<script>
var directory = [
{ type: 'file', name: 'file1.txt' },
{ type: 'file', name: 'file2.txt' },
{
type: 'directory',
name: 'HTML Files',
files: [
{ type: 'file', name: 'file1.html' },
{ type: 'file', name: 'file2.html' }
]
},
{ type: 'file', name: 'file3.txt' },
{
type: 'directory',
name: 'JavaScript Files',
files: [
{ type: 'file', name: 'file1.js' },
{ type: 'file', name: 'file2.js' },
{ type: 'file', name: 'file3.js' }
]
}
];
function generateList(fdList) {
let mainUl = document.createElement("UL");
for (let i = 0; i < fdList.length; ++i) {
let newLi = document.createElement("LI");
let fd = fdList[i];
let textNode = null;
let fdName = null;
if (fd.type === 'file') {
fdName = fd.name.split('.').splice(0, 1)[0];
textNode = document.createTextNode(fdName);
newLi.appendChild(textNode);
} else {
textNode = document.createTextNode(fd.name);
newLi.appendChild(textNode);
newLi.appendChild(generateList(fd.files));
}
mainUl.appendChild(newLi);
}
return mainUl;
}
document.body.appendChild(generateList(directory));
</script>
</html>
I know there are already answers, but here is a fun solution using Array.map and templating. It is also recursive and can therefore handle n depth.
var directory = [
{ type: 'file', name: 'file1.txt' },
{ type: 'file', name: 'file2.txt' },
{
type: 'directory',
name: 'HTML Files',
files: [
{ type: 'file', name: 'file1.html' },
{ type: 'file', name: 'file2.html' }
]
},
{ type: 'file', name: 'file3.txt' },
{
type: 'directory',
name: 'JavaScript Files',
files: [
{ type: 'file', name: 'file1.js' },
{ type: 'file', name: 'file2.js' },
{ type: 'file', name: 'file3.js' }
]
}
];
const getName = (fileName) => fileName.split('.')[0];
const renderFile = (file) => `<li>${getName(file.name)}</li>`;
const renderDirectory = (data) => data.map(item => (item.type === 'file') ? renderFile(item) : `<li>${item.name}:<ul>${renderDirectory(item.files)}</ul></li>`).join('');
document.addEventListener('DOMContentLoaded', () => {
let ul = document.createElement('ul');
ul.innerHTML = renderDirectory(directory);
document.querySelector('#target').appendChild(ul);
});
<div id="target"></div>

How to recursively render a tree view of a directory using FS from node

so suppose I have this directory:
Root
includes
header.php
footer.php
css
images
logos
white.svg
dark.svg
gallery
1.jpg
2.jpg
3.jpg
4.jpg
5.jpg
contact-page.jpg
style.css
js
jquery.js
main.js
index.php
contact.php
about-us.php
I'm parsing the directory recursively using this answer from another stack overflow post.
It returns an array with objects with all the files and folders, if it's a folder, then it has a 'children' array with all sub files or sub folders an so.
How can I iterate through this object to render that into HTML using <ul> and <li> elements. I know it must use a recursive function but I can't wrap my head around it. Also, it can use jQuery to append elements.
Thanks in advance!
EDIT: http://pastebin.com/iQ3xbKpC there is the JSON
You could use this recursive function:
function listHtml(children) {
return '<ul>' + children.map(node =>
'<li>' + node.name +
(node.type === 'file' ? '' : listHtml(node.children)) +
'</li>').join('\n') +
'</ul>';
}
// Sample data
var data = [
{ name: 'Root', type: 'folder', children: [
{ name: 'includes', type: 'folder', children: [
{ name: 'header.php', type: 'file' },
{ name: 'footer.php', type: 'file' },
]},
{ name: 'css', type: 'folder', children: [
{ name: 'images', type: 'folder', children: [
{ name: 'logos', type: 'folder', children: [
{ name: 'white.svg', type: 'file' },
{ name: 'dark.svg', type: 'file' },
]},
{ name: 'gallery', type: 'folder', children: [
{ name: '1.jpg', type: 'file' },
{ name: '2.jpg', type: 'file' },
{ name: '3.jpg', type: 'file' },
{ name: '4.jpg', type: 'file' },
{ name: '5.jpg', type: 'file' },
]},
{ name: 'contact-page.jpg', type: 'file' },
]},
{ name: 'style.css', type: 'file' },
]},
{ name: 'js', type: 'folder', children: [
{ name: 'jquery.js', type: 'file' },
{ name: 'main.js', type: 'file' },
]},
{ name: 'index.php', type: 'file' },
{ name: 'contact.php', type: 'file' },
{ name: 'about-us.php', type: 'file' },
]}
];
// transform to HTML
var html = listHtml(data);
// Inject into document
document.body.insertAdjacentHTML( 'beforeend', html );

Categories