Recursion with DOM in JavaScript - javascript

I am learning JavaScript and I was trying to solve a question given at https://javascript.info/modifying-document#create-a-tree-from-the-object.
The question was to create a nested ul/li from the nested object given.
The following is my code:
let data =
{
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
function createTree(data,key=null,parent_element=document.body){
if(Object.keys(data).length){
if(key){
li = document.createElement('li');
li.textContent = key;
li = parent_element.append(li);
}
ul = document.createElement('ul');
parent_element.append(ul);
for(let key in data){
createTree(data[key],key,ul);
}
return
}
li = document.createElement('li');
li.textContent = key;
parent_element.append(li);
return;
}
createTree(data);
This produces the following output
while the expected output is the following
What is wrong with my code? I can't find anything wrong with my logic.

There is nothing wrong with your logic. The problem is, you forgot to put a var declaration before your ul variable in your createTree function. Add var before it and your code works. (You should ALWAYS declare variables with var, let, or const or things can get weird.)
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
function createTree(data, key = null, parent_element = document.body) {
var li;
if (Object.keys(data).length) {
if (key) {
li = document.createElement('li');
li.textContent = key;
li = parent_element.append(li);
}
var ul = document.createElement('ul');
parent_element.append(ul);
for(let key in data){
createTree(data[key], key, ul);
}
return;
}
li = document.createElement('li');
li.textContent = key;
parent_element.append(li);
return;
}
createTree(data);
Here's a quick breakdown of the different ways to declare variables in javascript and what each one means:
// Creates a global variable.
myVar1 = 1;
// Creates a variable within the scope you're currently in. It's "hoisted"
// to the top of the scope you're currently in, so if you declare a var in
// the middle of a function, it gets pulled to the very top when your code
// is executed.
var myVar2 = 2;
// Declares a variable that is not hoisted.
let myVar3 = 3;
// Declares a constant that cannot be reassigned.
const myVar4 = 4;
The reason your implementation failed is because ul became a global variable which caused your function to not return a desirable result.

let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"redbud": {},
"magnolia": {}
}
}
};
function createTree(data, key = null, parent_element = document.body) {
if (Object.keys(data).length) {
let ul = document.createElement('ul');
if (key) {
li = document.createElement('li');
li.textContent = key;
li = parent_element.append(li);
}
ul = document.createElement('ul');
parent_element.append(ul);
for (let key in data) {
createTree(data[key], key, ul);
}
return
}
li = document.createElement('li');
li.textContent = key;
parent_element.append(li);
return;
}
createTree(data);
You need to create let ul = document.createElement('ul'); then need to append <li> was creating issue...

Related

Display html from JS array

I am working on displaying an array of HTML elements from JavaScript that looks like this:
Here is my code:
function createOrderedList(items) {
var ol = document.createElement("ol");
items.forEach(function(item) {
ol.appendChild(createListItem(item));
});
return ol;
}
function createListItem(item) {
var li = document.createElement("li");
li.textContent = item;
return li;
}
var array = ["a", "b", "c", "d", "e"];
var list = array.reduceRight(function(p, c) {
var el = createOrderedList([c]);
if (p == null) {
p = el;
} else {
el.appendChild(p);
}
return el;
}, null);
document.querySelector(".content").appendChild(list);
<div class='content'></div>
I want it to look like the picture , for now I have something like this :
https://jsfiddle.net/ys0fp5bd/
I want make to make that the first one is equal the last one and the each next li is equal on the same level .

Create Html Tree view with native javascript / HTML

I need to create an HTML/CSS tree view as in the example from already created object using native javascript.
Please suggest,
BR
You could first build nested structure and then use recursive approach to also create html from that data where if the current element has children property you call the function again with that children array as a data parameter.
var data = [{"name":"container-1","type":"container","description":"container description"},{"name":"category-1","type":"category","parent":"container-1"},{"name":"grid-1","type":"grid","parent":"category-1"},{"name":"chart-1","type":"chart","parent":"category-1"},{"name":"container-2","type":"container"},{"name":"category-2","type":"category","parent":"container-2"},{"name":"category-3","type":"category","parent":"container-2"},{"name":"grid-2","type":"grid","parent":"category-2"},{"name":"chart-2","type":"chart","parent":"category-2"},{"name":"grid-3","type":"grid","parent":"category-3"}]
function toTree(data, pid = undefined) {
return data.reduce((r, e) => {
if (pid == e.parent) {
const obj = { ...e }
const children = toTree(data, e.name)
if (children.length) obj.children = children;
r.push(obj)
}
return r
}, [])
}
function toHtml(data, isRoot = true) {
const ul = document.createElement('ul')
if (!isRoot) {
ul.classList.add('hide')
}
data.forEach(e => {
let isVisible = isRoot;
const li = document.createElement('li')
const text = document.createElement('span')
const button = document.createElement('button')
if (e.children) {
button.textContent = '+'
li.appendChild(button)
}
text.textContent = e.name
li.appendChild(text)
if (e.children) {
const children = toHtml(e.children, false)
li.appendChild(children)
button.addEventListener('click', function() {
if (isRoot) {
isVisible = !isVisible
}
button.textContent = isVisible ? '+' : '-'
children.classList.toggle('hide')
if (!isRoot) {
isVisible = !isVisible
}
})
}
ul.appendChild(li)
})
return ul;
}
const tree = toTree(data)
const html = toHtml(tree)
document.body.appendChild(html)
.hide {
display: none;
}
button {
margin-right: 10px;
}

generate elements based on key structure of object

I have an multidimensional object. Now I wanna generate DOM-elements based on the key structure of this object.
As a default view all root keys should be shown as div elements. With a click on one of this elements the divs should be replaced with the direct children of the clicked key.
My current version looks like this
object:
let object = {
"1.0": {
"1.0.1": {},
"1.0.2": {},
},
"1.1": {
"1.1.1": {
"1.1.1.1": {},
},
"1.1.2": {},
},
};
this is my recursive function to generate DOM elements for each key:
function categoryTree(obj) {
for (var key in obj) {
categoryContainer.innerHTML += "<div>" + key + "</div>";
categoryTree(obj[key]);
}
}
Now, I don't know how to make this interactive and show the child keys only when the parent was clicked.
You could build nested html structure with createElement and for...in loop. And then you can also add event listener on div that will toggle its children display property.
let object = {
"1.0": {
"1.0.1": {},
"1.0.2": {}
},
"1.1": {
"1.1.1": {
"1.1.1.1": {}
},
"1.1.2": {}
}
}
let categoryContainer = document.querySelector(".categoryContainer")
function categoryTree(obj, parent, start = true) {
for (var key in obj) {
let div = document.createElement("div");
div.textContent = key;
if (parent.children) parent.className += " bold";
if (!start) div.className = "normal hide"
div.addEventListener('click', function(e) {
e.stopPropagation()
Array.from(div.children).forEach(child => {
child.classList.toggle('hide')
})
})
categoryTree(obj[key], div, false)
parent.appendChild(div)
}
}
categoryTree(object, categoryContainer)
.hide {display: none;}
.normal {font-weight: normal;}
.bold {font-weight: bold;}
<div class="categoryContainer"></div>
Just use the DOM methods:
const n = (type, settings = {}) => Object.assign(document.createElement(type), settings);
function treeNode(name, children) {
const text = n("p", { textContent: name });
const container = n("div");
container.style.display = "none";
for(const [childName, child] of Object.entries(children))
container.appendChild(treeNode(childName, child));
const node = n("div");
node.appendChild(text);
node.appendChild(container);
node.onclick = () => container.style.display = "block";
return node;
}
categoryContainer.appendChild(treeNode("root", object));

JS - how can I dynamically add data and append it to DOM without a page refresh in vanilla javascript?

I have a todo list where I want to show all todos, todos flagged false (on click) and todos flagged true (on click). Each item has (for now here) only a title and a button, which (when clicked) flaggs the item (true).
I couldnt really find anything online that helped me. Any help much appreciated!
I have created a codepen so you can (hopefully) easier see the problem.
https://codepen.io/helloocoding/pen/XEXJJN?editors=1010
How can I do the following:
1:
For all flagged items: When I click on the 'showFlagged' button I am being shown flagged items which is what I want. However, it should also show the newly added item too (without a page refresh). Here what I did was, calling the showNotFlagged function inside the function where I set flag to true. However, like this, when you click on the flag button (next to the added item), the whole list of showNotFlagged items is being shown.
How can I call the showFlagged function only if its already 'open', so add it to dom but not show it yet?
2:
For not flagged items:
when I add new item, its flagged automatically false. How can I add the newly added item 'show Not Flagged list' without adding it to the dom yet? I cant call the function where I append all not flagged items because I just want to add new items, but not to the DOM yet.
HTML:
<div>
<input id="title" />
<button type="submit" id="add">Add me</button>
</div>
<ul id="todoList"></ul>
<button type="submit" id="showFlagged">Show Flagged</button>
<button type="submit" id="showNotFlagged">Show not Flagged</button>
<ul id="flaggedTodos"></ul>
JS:
function Todo(title) {
this.title = title;
this.flag = false;
}
window.onload = init;
function init() {
//showAll();
var addButton = document.querySelector("#add");
addButton.onclick = addItem;
var flagedButton = document.querySelector("#showFlagged");
flagedButton.onclick = showFlaggedTodos;
var notFlagedButton = document.querySelector("#showNotFlagged");
notFlagedButton.onclick = showNotFlaggedTodos;
}
function showAll() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
var todos = JSON.parse(localStorage.getItem("todos")) || [];
todos.map(function(item) {
var li = create(item);
listFragment.appendChild(li);
})
ul.appendChild(listFragment);
}
function addItem() {
var title = document.querySelector("#title").value;
var todoItem = new Todo(title);
var ul = document.getElementById("todoList");
var li = create(todoItem);
ul.appendChild(li);
saveItem(todoItem)
}
function create(todoItem) {
var li = document.createElement("li");
var titleElem = document.createElement("li");
var title = document.createTextNode(todoItem.title);
titleElem.appendChild(title);
var button = document.createElement("button");
var buttonFlag = document.createTextNode("flag me");
button.appendChild(buttonFlag);
button.addEventListener("click", function(ev) {
isFlagged(todoItem, ev);
})
li.appendChild(titleElem);
li.appendChild(button);
return li;
}
function saveItem(todoItem) {
var todos = JSON.parse(localStorage.getItem("todos")) || [];
todos.push(todoItem);
localStorage.setItem("todos", JSON.stringify(todos));
}
function updateItem(todoItem) {
console.log("3", todoItem)
var todos = JSON.parse(localStorage.getItem("todos")) || [];
var updatedItem = todoItem;
todos.map(function(item, i) {
if (todoItem.title == item.title) {
todos.splice(i, 1);
todos.push(updatedItem);
}
})
localStorage.setItem("todos", JSON.stringify(todos));
}
function isFlagged(todoItem, ev) {
todoItem.flag = !todoItem.flag;
updateItem(todoItem);
showFlaggedTodos();
}
function showFlaggedTodos() {
console.log("called")
var ul = document.getElementById("flaggedTodos");
var listFragment = document.createDocumentFragment();
var todos = JSON.parse(localStorage.getItem("todos")) || [];
todos.map(function(item) {
if (todos.length > 0) {
if (item.flag) {
var li = create(item);
listFragment.appendChild(li);
} else {
console.log("no flagged items")
}
} else {
console.log("no items")
}
})
ul.appendChild(listFragment);
var flagedButton = document.querySelector("#showFlagged");
flagedButton.onclick = "";
}
function showNotFlaggedTodos() {
var ul = document.getElementById("flaggedTodos");
var listFragment = document.createDocumentFragment();
var todos = JSON.parse(localStorage.getItem("todos")) || [];
todos.map(function(item) {
if (todos.length > 0) {
if (!item.flag) {
var li = create(item);
listFragment.appendChild(li);
} else {
console.log("no not flagged items")
}
} else {
console.log("no items")
}
})
ul.appendChild(listFragment);
var notFlagedButton = document.querySelector("#showNotFlagged");
notFlagedButton.onclick = "";
}

Knockoutjs: Remove/add element from array nested in viewmodel

I want to remove and add an element from an array which is nested in a ko.observable object. I'm using the ko.mapping utility to map json data to my viewmodel. Inside the json data i have an array and it is this array that i want to remove and add an element from.
The add and remove functions are call from HTML bindings.
See my current code for doing this. It is not elegant at all, i know that, that is why i'm asking for help. How do i do see smarter?
function BaseViewModel() {
var self = this;
self.newItem = null;
self.selectedItem = ko.observable();
self.getNewItem = function () {
return self.newCleanItem(self.newItem);
}
self.read = function (search, callback) {
self.baseService.read(search, callback);
}
self.readCallback = function (data) {
if (self.newItem == null)
self.newItem = data;
self.selectedItem(data);
showInputContainer();
}
self.addLog = function () {
var item = new self.getNewItem();
var newItem = item.tLogs[0];
var currentSelectedItem = ko.mapping.toJS(self.selectedItem);
currentSelectedItem.tLogs.push(newItem);
self.selectedItem(currentSelectedItem);
showInputContainer(activeTab);
};
self.removeLog = function (item) {
var currentSelectedItem = ko.mapping.toJS(self.selectedItem);
currentSelectedItem.tLogs.pop(item);
vm.selectedItem(currentSelectedItem);
showInputContainer();
}
self.newCleanItem = function (data) {
for (var d in data) {
if (Object.prototype.toString.call(data[d]) === '[object Array]') {
var array = data[d];
for (var item in array) {
if (framework.baseFunctions().isNumeric(item)) {
for (var i in array[item]) {
array[i] = "";
}
}
}
data[d] = array;
}
else {
data[d] = "";
}
}
return data;
}
}
My jsondata could look that this:
jsondata = {
caseName: "test",
caseDescription: "This is a test",
tLogs: [
{
name: "log1",
date: "2013-03-19"
},
{
name: "log2",
date: "2013-02-02"
}
]
}

Categories