Is there any way to get a key-value from an object's parent object? In the example below, I want to combine urlParent with section:
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
subItems: [
{
subId: 1,
name: 'Project 1',
section: '#project1',
get url() {
//output /home#project1
}
}
]
}
];
console.log(linkItems[0].subItems[0].url) // /home#project1;
You cannot reference an Object parent that way (Object(value) ← Array ← Object), and not even from an Object's parent Object.
What you can do instead is:
Create two Classes, one for the parent and one for the child.
When adding a child to the parent, just make a "linked list", by referencing the parent's this to the created child item parent property
class Child {
constructor(data) {
Object.assign(this, data);
}
get url() {
return this.parent.urlParent + this.section
}
}
class Parent {
constructor(data) {
Object.assign(this, data);
this.subItems = [];
}
addChild(item) {
this.subItems.push(new Child({...item, parent: this}));
}
}
// Example:
const parent = new Parent({id:1, name:"Home", urlParent:"/home"});
parent.addChild({subId:1, name:"Project 1", section:"#project1"});
console.log(parent.subItems[0].url) // /home#project1;
But hey! Nodes and trees
Your original idea and the above use too much complexity.
What I'd suggest is to treat all parent, child, whatever, as Page Nodes.
class Page {
constructor(data) {
Object.assign(this, data);
this.children = {};
}
addChild(page) {
page.parent = this; // Linked to parent!
this.children[page.id] = page;
}
get url() {
// Generate full URI by recursing the parents tree
return this.parent ? `${this.parent.url}/${this.slug}` : this.slug;
}
}
// Example:
// 1. Create pages:
const pageRoot = new Page({id:1, name:"Home page", slug:""});
const pageProj = new Page({id:3, name:"All projects", slug:"projects"});
const pageWebs = new Page({id:4, name:"Websites", slug:"websites"});
const pageStOv = new Page({id:6, name:"Stack Overflow", slug:"so"});
const pageSpec = new Page({id:9, name:"Stack Overflow Specs", slug:"specs"});
// 2. Create Tree:
pageRoot.addChild(pageProj);
pageProj.addChild(pageWebs);
pageWebs.addChild(pageStOv);
pageStOv.addChild(pageSpec);
// 3. Test
console.log(pageRoot.url); // "";
console.log(pageProj.url); // "/projects";
console.log(pageSpec.url); // "/projects/websites/so/specs";
console.log(pageRoot);
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
get subItems(){
console.log(this.name);
return ([
(() => {
console.log(this);
return {
subId: 1,
name: 'Project 1',
section: '#project1',
urlParentFromOuterScope: () => {
return this.urlParent;
},
sectionString(){
return this.section;
},
url(){
console.log('url', this);
return this.urlParentFromOuterScope() + this.sectionString();
}
}
})()
])
}
}
];
const subItems = linkItems[0].subitems;
console.log(linkItems[0].subItems[0].url());
Please feel free to remove the unnecessary 'console.log's after you understand the approach.
I took the liberty of adding a few methods.
This is a tricky one and has to do with the scope of this in array functions.
P.S.: I guess this can be simplified.
Related
How do you set a multi-level deep update on an object with a dynamic key in javascript? When i try a typical dynamic update, it adds another key/value pair instead of updating the correct parameter.
let home = {
street: {
house: {
room: {
window: true
}
}
}
}
let update = {
key: "house.room.window",
value: "false"
}
home.street[update.key] = update.value;
console.log(home);
expected:
home = {
street:{
house: {
room: {
window: false
}
}
}
}
instead i get:
home = {
street:{
house: {
room: {
window: true
}
}
"house.room.window": false
}
}
Try like below. Explanation is in comments.
let home = {
street: {
house: {
room: {
window: true
}
}
}
}
let update = {
key: "house.room.window",
value: "false"
}
// select object to get updated
let obj = home.street;
// get array of nested keys
let nestedKeys = update.key.split('.');
// get object from nested keys until last key
// used slice(0, -1) so it will iterate through all key except last one
nestedKeys.slice(0, -1).forEach(k => obj = obj[k]);
// use object with last key and update value
obj[nestedKeys[nestedKeys.length - 1]] = update.value
// log object
console.log(home);
If you don't mind using a library, you can try lodash's update.
_.update(home, update.key, () => update.value);
In plain javascript, I am trying to create a function that will return a tree structure (json) of a folder, its subfolders and any files. I'm trying to achieve this using recursion.
The problem with the below code is that it stops after the first recursive call.
I know that in JS you do references, and I need to create a new object that I pass the values from the previous call to, but I am struggling to do so.
function fun(file, json) {
var tempJson = {
'name' : json.name || '',
'children' : obj.children || new Object()
};
if (file.type == 'file') {
tempJson.type = 'file';
tempJson.children = {}; // this will be empty, since there are no children
}
else {
tempJson.type = 'dir';
var listed = file.listFiles();
if (listed.length > 0) {
for each (var item in listed) {
tempJson.children = fun(item, tempJson);
}
} else {
tempJson.children = {};
}
}
return tempJson;
}
Example
From a directory structure like:
-root
--file1
--dir1
---file1.1
--dir2
I would like to get a json like:
{
name: 'root',
type: 'dir',
children : [
{
name: 'file1',
type: 'file',
children: {}
},
{
name: 'dir1',
type: 'dir',
children:
{
name: 'file1.1',
type: 'file',
children: {},
}
},
name: 'dir2',
type: 'dir',
children: {}
}
First call:
var object = new Object();
fun(rootdir, object);
Hope this makes sense.
Thanks!
As pointed out in the comments, children should be an array:
function fun(entry) {
var entryObj = { // construct the object for this entry
name: entry.name || "",
type: entry.type, // put the type here instead of using an if
children: [] // children must be an array
};
if(entry.type === "dir") { // if this entry is a directory
var childEntries = entry.listFiles(); // get its child entries
for(var childEntry of childEntries) { // and for each one of them
entryObj.children.push(fun(childEntry)); // add the result of the call of 'fun' on them to the children array
}
}
return entryObj;
}
Then call it like so:
var tree = fun(rootEntry);
I am trying to move an object from one array to another. Think of it like adding / moving a friend from non-friend to friend. I have two arrays, which can be seen below, and I am trying to move an object (i.e. a friend) from possible to current via it's 'id'. In the below example, I am trying to move Parker from possible to current with id = 2.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = Object.assign(
{},
state.possible.splice(action.payload.index, 1)
);
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
Since you can remove multiple elements with splice(), it returns an array. Index the result to get the specific object. You don't need to use Object.assign(), that just copies the value (which just converts the array into an object whose properties are the array indexes).
var state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
};
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = state.possible.splice(action.payload.index, 1)[0];
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
state = addFriend(state, {payload: { index: 0 }});
console.log(state);
I'm not sure why you're returning a new state object, since you're modifying the old state in place.
It is not that time-efficient if you want a fast running code. But it follows immutability.
We just ignore the item from possible, and add it to current.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
return {
...state,
current: current.concat(possible[action.payload.index]),
possible: possible.filter((_, index) => index !== action.payload.index)
}
}
state = addFriend(state, {payload: {index: 0}})
console.log(state)
I am working on a treeview and I created a class Node, consisting of a name and a list of children.
class Node {
constructor(name, childNodes) {
this.name = name;
this.childNodes = childNodes;
}
}
I am trying to create an object looking like the object "tree" in the image below. Every Node has a attribute called "nodes" which is the childNode-Array.
I got very close to creating the same tree, but in my version there is an extra "Array"-Layer around each of the Child nodes (see the recTreeview()-Element):
I am not sure how to fix this problem. The array has to be created to contain the childnodes. Here is the code where the treeview Element is created:
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.children.forEach(child => {
tempChildren.push(recTreeview(child, []));
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}
Is there anything I can do? Update: A Code-Snippet:
class Node {
constructor(name, children) {
this.children = children;
this.name = name;
}
}
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.children.forEach(child => {
tempChildren.push(recTreeview(child, []));
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}
child1 = new Node("child1", []);
child2 = new Node("child2", []);
parent = new Node("Parent", [child1, child2]);
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1"
},
{
text: "Child 2"
}
]
}
];
<button onClick="console.log(recTreeview(parent, []));">Wrong</button>
<button onClick="console.log(tree);">Right</button>
The extra array is comming from tempChildren.push(recTreeview(child, []));. Because you are pushing the return value of recTreeview (which is an array) to the children array tempChildren.
Instead of pushing the new array to tempChildren, you should pass tempChildren as the parameter to recTreeview:
currentNode.children.forEach(child => {
recTreeview(child, tempChildren);
});
So that the children will be pushed directly to the tempChildren array instead of getting wrapped in the extra array recTreeview is creating.
Example:
class Node {
constructor(name, children) {
this.children = children;
this.name = name;
}
}
function recTreeview(currentNode, treeview) {
var tempChildren = [];
currentNode.children.forEach(child => {
recTreeview(child, tempChildren);
});
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
return treeview;
}
child1 = new Node("child1", []);
child2 = new Node("child2", []);
parent = new Node("Parent", [child1, child2]);
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1"
},
{
text: "Child 2"
}
]
}
];
<button onClick="console.log(recTreeview(parent, []));">Not Wrong Anymore</button>
<button onClick="console.log(tree);">Right</button>
Note:
If you want to get rid of that empty nodes array if there are no children, then change this:
treeview.push({
text: currentNode.name,
nodes: tempChildren
});
To:
var node = {
text: currentNode.name
};
if(tempChildren.length) {
node.nodes = tempChildren;
}
treeview.push(node);
Which only adds the nodes property if the tempChildren array is not empty.
I currently have this object:
var obj = {
1: {
title: 'test',
children: {
2: {
title: 'test2',
children: {}
},
3: {
title: 'test3',
children: {}
}
}
}
};
The whole idea is I make a function to add an item to this object. As parameter I send the parent.
Now, I was wondering how I would get the right item object. For example if I send parent '2', it would get 2: from the children of 1:. The only way I can think of is a for loop, but I don't know if there's a more efficient way. The children can be extended even more, so a parent has children, those children have children endlessly. That's the whole idea at least.
I think with a few items a for loop is okay, but I think if I have over 50 items it's already slow, and it'll even be slower with more.
This solution use Object.keys() for getting all keys of the given object and an array iteration with short ciruit Array.prototype.some() looks for the key. If found the reference is returned, otherwise the item is checked for an object. If so the object reference is taken for a new search with getReference().
var obj = { 1: { title: 'test', children: { 2: { title: 'test2', children: {} }, 3: { title: 'test3', children: {} } } } };
function getReference(o, p) {
var r;
Object.keys(o).some(function (k) {
if (k === p) {
r = o[k];
return true;
}
if (typeof o[k] === 'object') {
r = getReference(o[k], p);
return !!r;
}
});
return r;
}
var x = getReference(obj, '2');
document.write(x.title);
If you want adding to be fast, you can preserve indexes of your child nodes in object or map (ES6). It could look like this:
function Tree() {
this.data = {};
this.indexes = {0: this.data};
}
Tree.prototype = {
addNode: function(parentIndex, index, node) {
// handle cases when parentIndex does not exist
// handle cases when index already exists
this.indexes[index] = node;
var parent = this.indexes[parentIndex];
parent.children = parent.children || {};
parent.children[index] = node;
}
}
var tree = new Tree();
tree.addNode(0, 1, { title: 'test' });
tree.addNode(1, 2, { title: 'test2' });
tree.addNode(1, 3, { title: 'test3' });
console.log(tree.data);