Skip to content

Commit 4c05ce6

Browse files
committed
Add map and getTreeFromFlatData util methods
map() transforms every node in the tree with the callback given. getTreeFromFlatData() is a convenience method for generating nested tree data from a flat array of objects.
1 parent 561a372 commit 4c05ce6

File tree

3 files changed

+480
-15
lines changed

3 files changed

+480
-15
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"karma-spec-reporter": "0.0.26",
7373
"karma-webpack": "^1.7.0",
7474
"node-sass": "^3.8.0",
75-
"postcss-loader": "^0.9.1",
75+
"postcss-loader": "^0.10.0",
7676
"react": "^15.3.0",
7777
"react-addons-shallow-compare": "^15.3.0",
7878
"react-dom": "^15.3.0",

src/utils/tree-data-utils.js

Lines changed: 159 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,67 @@ function walkDescendants({
106106
}
107107
}
108108

109-
// Flatten all descendant arrays into a single flat array
110109
return childIndex;
111110
}
112111

112+
/**
113+
* Perform a change on the given node and all its descendants
114+
*/
115+
function mapDescendants({
116+
callback,
117+
getNodeKey,
118+
ignoreCollapsed,
119+
isPseudoRoot = false,
120+
node,
121+
currentIndex,
122+
path = [],
123+
lowerSiblingCounts = [],
124+
}) {
125+
// The pseudo-root is not considered in the path
126+
const selfPath = !isPseudoRoot ? [ ...path, getNodeKey({ node, treeIndex: currentIndex }) ] : [];
127+
const selfInfo = !isPseudoRoot ? { node, path: selfPath, lowerSiblingCounts, treeIndex: currentIndex } : null;
128+
129+
// Return self on nodes with no children or hidden children
130+
if (!node.children || (node.expanded !== true && ignoreCollapsed && !isPseudoRoot)) {
131+
return {
132+
treeIndex: currentIndex,
133+
node: callback(selfInfo),
134+
};
135+
}
136+
137+
// Get all descendants
138+
let childIndex = currentIndex;
139+
const childCount = node.children.length;
140+
let newChildren = node.children;
141+
if (typeof newChildren !== 'function') {
142+
newChildren = newChildren.map((child, i) => {
143+
const mapResult = mapDescendants({
144+
callback,
145+
getNodeKey,
146+
ignoreCollapsed,
147+
node: child,
148+
currentIndex: childIndex + 1,
149+
lowerSiblingCounts: [ ...lowerSiblingCounts, childCount - i - 1 ],
150+
path: selfPath,
151+
});
152+
childIndex = mapResult.treeIndex;
153+
154+
return mapResult.node;
155+
});
156+
}
157+
158+
return {
159+
node: callback({
160+
...selfInfo,
161+
node: {
162+
...node,
163+
children: newChildren,
164+
},
165+
}),
166+
treeIndex: childIndex,
167+
};
168+
}
169+
113170
/**
114171
* Count all the visible (expanded) descendants in the tree data.
115172
*
@@ -134,7 +191,7 @@ export function getVisibleNodeCount({ treeData }) {
134191
*
135192
* @param {!Object[]} treeData - Tree data
136193
* @param {!number} targetIndex - The index of the node to search for
137-
* @param {function} getNodeKey - Function to get the key from the nodeData and tree index
194+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
138195
*
139196
* @return {{
140197
* node: Object,
@@ -172,7 +229,7 @@ export function getVisibleNodeInfoAtIndex({ treeData, index: targetIndex, getNod
172229
* Walk descendants depth-first and call a callback on each
173230
*
174231
* @param {!Object[]} treeData - Tree data
175-
* @param {function} getNodeKey - Function to get the key from the nodeData and tree index
232+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
176233
* @param {function} callback - Function to call on each node
177234
* @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
178235
*/
@@ -193,11 +250,37 @@ export function walk({ treeData, getNodeKey, callback, ignoreCollapsed = true })
193250
});
194251
}
195252

253+
/**
254+
* Perform a depth-first transversal of the descendants and
255+
* make a change to every node in the tree
256+
*
257+
* @param {!Object[]} treeData - Tree data
258+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
259+
* @param {function} callback - Function to call on each node
260+
* @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
261+
*/
262+
export function map({ treeData, getNodeKey, callback, ignoreCollapsed = false }) {
263+
if (!treeData || treeData.length < 1) {
264+
return [];
265+
}
266+
267+
return mapDescendants({
268+
callback,
269+
getNodeKey,
270+
ignoreCollapsed,
271+
isPseudoRoot: true,
272+
node: { children: treeData },
273+
currentIndex: -1,
274+
path: [],
275+
lowerSiblingCounts: [],
276+
}).node.children;
277+
}
278+
196279
/**
197280
* Get visible node data flattened.
198281
*
199282
* @param {!Object[]} treeData - Tree data
200-
* @param {function} getNodeKey - Function to get the key from the nodeData and tree index
283+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
201284
*
202285
* @return {{
203286
* node: Object,
@@ -229,7 +312,7 @@ export function getVisibleNodeInfoFlattened({ treeData, getNodeKey }) {
229312
* @param {!Object[]} treeData
230313
* @param {number[]|string[]} path - Array of keys leading up to node to be changed
231314
* @param {function|any} newNode - Node to replace the node at the path with, or a function producing the new node
232-
* @param {function} getNodeKey - Function to get the key from the nodeData and tree index
315+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
233316
* @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
234317
*
235318
* @return {Object} changedTreeData - The updated tree data
@@ -321,7 +404,7 @@ export function changeNodeAtPath({ treeData, path, newNode, getNodeKey, ignoreCo
321404
*
322405
* @param {!Object[]} treeData
323406
* @param {number[]|string[]} path - Array of keys leading up to node to be deleted
324-
* @param {function} getNodeKey - Function to get the key from the nodeData and tree index
407+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
325408
* @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
326409
*
327410
* @return {Object} changedTreeData - The updated tree data
@@ -342,7 +425,7 @@ export function removeNodeAtPath({ treeData, path, getNodeKey, ignoreCollapsed =
342425
* @param {!Object[]} treeData
343426
* @param {!Object} newNode - The node to insert
344427
* @param {number[]|string[]} path - Array of keys leading up to node to be deleted
345-
* @param {function} getNodeKey - Function to get the key from the nodeData and tree index
428+
* @param {!function} getNodeKey - Function to get the key from the nodeData and tree index
346429
* @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true`
347430
*
348431
* @return {Object} changedTreeData - The updated tree data
@@ -355,13 +438,75 @@ export function addNodeUnderParentPath({
355438
getNodeKey,
356439
ignoreCollapsed = true
357440
}) {
358-
// return changeNodeAtPath({
359-
// treeData,
360-
// path: newParentPath,
361-
// getNodeKey,
362-
// ignoreCollapsed,
363-
// newNode: (node) => typeof node.children === 'object' ? , // Delete the node
364-
// });
441+
return changeNodeAtPath({
442+
treeData,
443+
getNodeKey,
444+
ignoreCollapsed,
445+
path: newParentPath,
446+
newNode: ({ node: parentNode }) => {
447+
if (typeof parentNode.children === 'function') {
448+
throw new Error('Cannot add to children defined by a function');
449+
}
450+
451+
return {
452+
...parentNode,
453+
children: !parentNode.children ? [ newNode ] : [
454+
...parentNode.children.slice(0, newChildIndex),
455+
newNode,
456+
...parentNode.children.slice(newChildIndex + 1),
457+
],
458+
};
459+
},
460+
});
461+
}
462+
463+
/**
464+
* Generate a tree structure from flat data.
465+
*
466+
* @param {!Object[]} flatData
467+
* @param {!function} getKey - Function to get the key from the nodeData
468+
* @param {!function} getParentKey - Function to get the parent key from the nodeData
469+
*
470+
* @return {Object[]} treeData - The flat data represented as a tree
471+
*/
472+
export function getTreeFromFlatData({
473+
flatData,
474+
getKey,
475+
getParentKey,
476+
rootKey,
477+
}) {
478+
if (!flatData) {
479+
return [];
480+
}
481+
482+
const childrenToParents = {};
483+
flatData.forEach(child => {
484+
const parentKey = getParentKey(child);
485+
486+
if (parentKey in childrenToParents) {
487+
childrenToParents[parentKey].push(child);
488+
} else {
489+
childrenToParents[parentKey] = [ child ];
490+
}
491+
});
492+
493+
if (!(rootKey in childrenToParents)) {
494+
return [];
495+
}
496+
497+
const trav = (parent) => {
498+
const parentKey = getKey(parent);
499+
if (parentKey in childrenToParents) {
500+
return {
501+
...parent,
502+
children: childrenToParents[parentKey].map(child => trav(child)),
503+
};
504+
}
505+
506+
return { ...parent };
507+
};
508+
509+
return childrenToParents[rootKey].map(child => trav(child));
365510
}
366511

367512
// Performs change to every node in the tree

0 commit comments

Comments
 (0)