I have the following recursive javascript function which is looping over the children of a backbone.marionette CollectionView that has children ItemViews that are in turn CollectionViews:
findViewByCid: function(cid, children){
var col = (arguments.length === 1) ? this.children : children;
if(cid in col){
return col[cid];
}
for(child in col){
var grandChildren = col[child].children;
if(cid in grandChildren){
return grandChildren[cid];
}
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
return this.findViewByCid(cid, grandChildren);
}
}
}
I am calling it like this:
var view = DocumentManager.Documents.treeRoot.findViewByCid(model.cid);
The problem is the line:
return this.findViewByCid(cid, grandChildren);
If I have a hierarchy like this
c1
|_c2
|_c3
|_c4
|_c5
Then te return statement will cause the function to exit after passing th3 c2 node and never get to c4 etc.
If I remove the return statement, the correct child is found but null is returned.
How can I continue parsing the hierarchy and return a value?
return will exit your function.
Try to save everything in a var and return at the very end, it can be an array if you need to return more than one value.
(and don't declare vars in a for-loop!)
Here is a suggestion :
findViewByCid: function(cid, children){
var willBeReturned=[];
var grandChildren;
var col = (arguments.length === 1) ? this.children : children;
if(cid in col){
willBeReturned[willBeReturned.length] = col[cid];
}
for(child in col){
grandChildren = col[child].children;
if(cid in grandChildren){
willBeReturned[willBeReturned.length] = grandChildren[cid];
}
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
willBeReturned[willBeReturned.length] = this.findViewByCid(cid, grandChildren);
}
}
return willBeReturned;
}
You will need to return only if something was found, else the return statement will break your loop without having searched the other children. It is a simple depth-first-search what you want.
Assuming the function is on the prototype of each child (not only on the root node):
findViewByCid: function(cid) {
var col = this.children;
if (!col) // break if the node has no children
return false;
if (cid in col) // look for cid and return the node if one found
return col[cid];
for (var child in col) {
// search through each child and return the result if something is found
var found = col[child].findViewByCid(cid);
if (found)
return found;
}
// else nothing was found
return false;
}
Or having a function taking a node as an argument:
function findViewByCid(cid, node) {
var col = node.children;
if (!col)
return false;
if (cid in col)
return col[cid];
for (var child in col) {
var found = findViewByCid(cid, col[child]);
if (found)
return found;
}
return false;
}
However, it seems this algorithm will not be able to find the root node. It would be better if you could identify the current node by cid, instead of looking at all of its children:
if (this /*… is what we have searched for */)
return this;
findViewByCid: function(cid, children) {
var col = (arguments.length === 1) ? this.children : children;
if(cid in col){
return col[cid];
}
for(var childKey in col) {
var grandChildren = col[childKey].children,
childView;
if (grandChildren) {
childView = this.findViewByCid(cid, grandChildren);
}
if (childView) {
return childView;
}
}
return null;
}
First of all, this looks like Backbone.js, it may be helpful to tag it if it is. I feel like people might have struggled with similar problems and know better ways to store references to views.
You only want to return something if it was found... just using return on the first recursive call will force the method to stop execution upon searching the first set of grandchildren, even if nothing was found.
I would also add a var in front of the new variable you're introducing in your for loop - without it, the variable is going to be global.
This is what I ended up with and it is backbone.marionette, iterating over a collectionView with an itemView of a CollectionView:
findViewByCid: function(cid){
var self = this,
ret;
function findView(cid, children){
var col = (arguments.length === 1) ? self.children : children,
grandChildren;
if(cid in col){
ret = col[cid];
}
for(child in col){
grandChildren = col[child].children;
if(cid in grandChildren){
ret = grandChildren[cid];
}
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
findView(cid, grandChildren);
}
}
};
findView(cid);
return ret;
}
I believe the line if (cid in col) does not what you want to do. Try
findViewByCid: function(cid, children){
if (this.cid === cid) return this;
var col = (arguments.length === 1) ? this.children : children;
for(var childI in col){
var child = col[childI];
if (child.cid === cid) {
return child;
}
var grandChildren = child.children;
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
return this.findViewByCid(cid, grandChildren);
}
}
}
Related
I am playing around with a binary tree. I am trying to use recursion to find all the nested children's values and push all the values into an array. I started with the left tree to see if it works. I tried to call childrenArray() but the console says childrenArray() is not defined. When I ask if (typeof BinaryTree.prototype.childrenArray === 'function'), it returns true. Please teach me and tell me why I wasn't able to execute my code?
var Tree = function(value) {
if (!(this instanceof Tree)) {
return new Tree(value);
}
this.value = value;
this.children = [];
};
Tree.prototype.addChild = function(value) {
var child = new Tree(value);
this.children.push(child);
};
Tree.prototype.contains = function(value) {
if (this.value === value) {
return true;
} else {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] && this.children[i].contains(value)) {
return true;
}
}
return false;
}
};
var BinaryTree = function(value) {
if (!(this instanceof BinaryTree)) {
return new BinaryTree(value);
}
Tree.call(this, value);
};
BinaryTree.prototype = Object.create(Tree.prototype);
BinaryTree.prototype.addChild = function(value) {
if (value < this.value) {
if (this.children[0] === undefined) {
this.children[0] = new BinaryTree(value);
}
this.children[0].addChild(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
this.children[1] = new BinaryTree(value);
}
this.children[1].addChild(value);
}
};
BinaryTree.prototype.contains = function(value) {
if (value < this.value) {
if (this.children[0] === undefined) {
return false;
}
return this.children[0].contains(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
return false;
}
return this.children[1].contains(value);
}
};
var a = new BinaryTree();
a.value = 10;
a.addChild(4);
a.addChild(11);
a.addChild(3);
BinaryTree.prototype.childrenArray = function() {
var results = [];
if (this.value) {
results.push(this.value);
}
if (this.children[0].length === 0) {
return results;
}
for (var i = 0; i < this.children[0].children.length; i++) {
if (this.children[i].value) {
results.push(this.children[i].value);
return this.childrenArray();
}
}
};
a.childrenArray();
As #melpomene mentioned, you are invoking childArray but you didn't define it anywhere. I assume the line return childArray(); ended up there by mistake, you probably meant to recursively return childrenArray for the left child of root.
I would like to mention that your loop itself (without the recursive call):
for(var i = 0; i < this.children[0].children.length; i++) { // <-- you are iterating over the children of root's left child
if(this.children[i].value) { // <-- but you are accessing root's current child
results.push(this.children[i].value);
}
}
is somewhat confusing. You are iterating over the children of root's left child children[0].children, but on each iteration you check if the root's children themselves children[i] have a value and you push that value.
This is incorrect and will break if, for example, the root only has a left child that has both children (i will be out of bounds).
Here's how you can approach this problem. The recursion to print the left tree in your case can be broken down into the following cases:
If the current node has no value, return an empty array
Else If the current node has no children, return an array only containing the current value
Else if the current node has a left child, run childrenArray on it recursively and add the results to the results array
Here's how that would look:
BinaryTree.prototype.childrenArray = function() {
var results = [];
// case 1:
if (this.value === undefined) {
return results;
}
// case 2:
results.push(this.value);
if (this.children.length === 0) {
return results;
}
// case 3:
var leftChild = this.children[0];
if (leftChild) {
results = results.concat(leftChild.childrenArray());
}
/* add code here for the right child to complete your function */
return results;
};
Full Example:
var BinaryTree = function(value) {
this.value = value;
this.children = [];
};
BinaryTree.prototype.addChild = function (value) {
if (value < this.value) {
if (this.children[0] === undefined) {
this.children[0] = new BinaryTree(value);
}
this.children[0].addChild(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
this.children[1] = new BinaryTree(value);
}
this.children[1].addChild(value);
}
};
BinaryTree.prototype.childrenArray = function() {
var results = [];
// case 1:
if (this.value === undefined) {
return results;
}
// case 2:
results.push(this.value);
if (this.children.length === 0) {
return results;
}
// case 3:
var leftChild = this.children[0];
if (leftChild) {
results = results.concat(leftChild.childrenArray());
}
/* add code here for the right child to complete your function */
return results;
};
var a = new BinaryTree(10);
a.addChild(4);
a.addChild(11);
a.addChild(3);
console.log(a.childrenArray()); // [10, 4, 3]
Last thing, based on your insertion logic (insert left if smaller, right if larger), you are building a Binary Search Tree not a Binary Tree. Binary tree's don't follow any particular insertion logic. Take a look at this question for clarification
I'm ultimately wanting to create a jQuery plugin that loops through the top level of the DOM and adds elements to an object until it gets to a heading, at which point it pushes an object with the text of that heading.
The following sibling elements would be added to this new object until a new heading is encountered — if the heading is at the same level, a new object is created as a sibling of the parent object as before, and DOM siblings are added to that instead of the first object; if the heading is at a lower level, that's added as a child of the first object, and sibling DOM elements are added as children to that heading object; if it's a higher level headline, a new object is added one level above the last heading object and the cycle continues.
Example:
<p>wooo</p>
<h1>stuff</h1>
<p>stuff</p>
<p>more stuff</p>
<h2>yet more stuff</h2>
<p>still more stuff</p>
<h3>even still more stuff</h3>
<p>yep — stuff!</p>
<h1>still yet more stuff</h1>
<p>stuff stuff stuff</p>
<p>stuff stuff stuffarino</p>
Becomes...
{
'p_wooo': HTMLElementObject,
'h1_stuff': {
'p_stuff': HTMLElementObject,
'p_more_stuff': HTMLElementObject,
'h2_yet_more_stuff': {
'p_still_more_stuff': HTMLElementObject,
'h3_even_still_more_stuff': {
'p_yep_stuff': HTMLElementObject,
}
},
},
'h1_still_yet_more_stuff': {
'p_stuff_stuff_stuff': HTMLElementObject,
'p_stuff_stuff_stuffarino': HTMLElementObject
{
}
Here's what I have so far:
var root = $(res)
.filter('#contents')
.children()
.not('style'); // Don't need no stylesheets hurr!
var sections = root.filter('h1');
var outline = {};
for (var i = 0; i < sections.length; i++) {
var children;
if (i+1 <= sections.length) {
children = root.filter(sections[i]).after().nextUntil(sections[i+1]).filter(function(){return $(this).text().trim() !== '';});
}
var slug = getSlug($(sections[i]).text(), {separator: '_'});
outline[slug] = children;
}
console.dir(outline);
Alas, it only works for H1s. How would I turn this into a recursive function that adds H2-H6s?
I'll start you with an example that traverses the nodes and adds them all into the same tree object. It should be fairly easy to figure out the rest from here:
JSBin: http://jsbin.com/bixekutuhe/1/edit?html,js,output
// Helpers
function isNode(el) { return el && el.nodeType === 1; }
function tag(el) { return el.tagName.toLowerCase(); }
var tree = {}, key;
var node = document.body.firstElementChild;
while (isNode(node) && tag(node) !== 'script') { // add blacklists or whitelists that you might need
key = node.textContent;
tree[node.tagName.toLowerCase() + '_' +key.split(' ').join('_')] = node;
node = node.nextElementSibling; // move to next element
}
console.log(tree);
Update
Try the following example instead:
var tree = {};
var currentTree = tree, tagName, key;
var node = document.body.firstElementChild;
function isNode(el) { return el && el.nodeType === 1; }
function tag(el) { return el.tagName.toLowerCase(); }
while (isNode(node)) {
tagName = tag(node);
key = tagName + '_' + node.textContent.trim().split(' ').join('_');
switch(tagName) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
if (tagName === 'h1') {
currentTree = tree[key] = {};
} else {
currentTree = currentTree[key] = {};
}
break;
default:
currentTree[key] = node;
break;
}
// Move to the next element
node = node.nextElementSibling;
}
console.log(tree);
Simply put
I have a tree structure made of objects.
Is it possible to build that tree and add to each object a reference to their parent ?
I know referencing works with objects, but i'm not sure if it would in that case?
I would like to be able to write something like this
currentLevel = this.getParent();
another exemple would be
this.getChildList().addChild({name: test,parent: this})
Without having copies and creating multiple tree from the first one.
2nd question
How would referencing works with array? Are they considered objects or does it depends on their content?
3nd question
Would saving the tree in the browser's cache, via string-JSON serialisation destroy the references?
You can do this be creating a "TreeNode" class:
var TreeNode = (function(){
//keep track of parent node
TreeNode.prototype.parent = null;
//keep track of children
TreeNode.prototype.children = [];
function TreeNode(parent) {
if(parent !== undefined) {
if(this.setParent(parent)) {
this.parent.addChild(this);
}
}
//...
}
TreeNode.prototype.setParent = function(parent) {
//add some sort of check to make sure it is a `TreeNode`
if(parent instanceof TreeNode) {
this.parent = parent;
return true;
}
return false;
}
TreeNode.prototype.addChild = function(child) {
//add some sort of check to make sure it is a `TreeNode`
if(child instanceof TreeNode) {
this.children.push(child);
child.setParent(this);
}
}
TreeNode.prototype.getParent = function(){
return this.parent;
}
TreeNode.prototype.getChildren = function(){
return this.children;
}
return TreeNode;
})();
And then you can expand from that.
Example Code:
var node_a = new TreeNode();
var node_b = new TreeNode(node_a);
var node_c = new TreeNode(node_a);
console.log(node_a.getParent(), node_c.get_parent()); //null , node_a
console.log(node_a.getChildren()); //[node_b, node_c]
This is just a start, it needs waaaaaaaaaay more expansion :-)
Okay, so there are most likely frameworks out there, but I wrote a quick thing which supports JSON serialisation and the reverse (via it's own methods). I took base inspiration from Neal's answer. Example
var a = new MyTreeNode('a'), // make some nodes
b = new MyTreeNode('b'),
c = new MyTreeNode('c');
a.addChild(b).addChild(c); // a parent of b parent of c
c.getParent() === b; // true
var str = a.toJSON(); // "{"nodeName":"a","childNodes":[{"nodeName":"b","childNodes":[{"nodeName":"c","childNodes":[]}]}]}"
MyTreeNode.parseJSON(str); // MyTreeNode (same structure as before)
Full code
/* MyTreeNode(String nodeName)
Instance Properties
- nodeName, String
- childNodes, Array of MyTreeNodes
- parentNode, MyTreeNode
Instance Methods
- addChild(MyTreeNode node), child MyTreeNode
- removeChild(MyTreeNode node), child MyTreeNode
- getParent, parent MyTreeNode
- getChildList, Array of MyTreeNodes
- serialise, JSON-safe Object
- toJSON, String
Constructor Methods
- deserialise(Object serialised), MyTreeNode
- parseJSON(String JSONString), MyTreeNode
*/
var MyTreeNode = (function () {
function MyTreeNode(nodeName) {
nodeName && (this.nodeName = nodeName);
this.childNodes = [];
}
MyTreeNode.prototype.parentNode = null;
MyTreeNode.prototype.childNodes = [];
MyTreeNode.prototype.nodeName = '';
// getters
MyTreeNode.prototype.getChildList = function () {
return this.childNodes = [];
};
MyTreeNode.prototype.getParent = function () {
return this.parentNode;
};
// add/remove
MyTreeNode.prototype.removeChild = function (node) {
var i = this.childNodes.indexOf(node);
if (node.parentNode !== this || i == -1)
throw new ReferenceError('node is not a child of this');
this.childNodes.splice(i, 1);
node.parentNode = null;
return node;
};
MyTreeNode.prototype.addChild = function (node) {
if (node.parentNode) node.parentNode.removeChild(node);
node.parentNode = this;
this.childNodes.push(node);
return node;
};
// JSON
MyTreeNode.prototype.serialise = function () {
var o = {
nodeName: this.nodeName,
childNodes: []
}, i;
for (i = 0; i < this.childNodes.length; ++i) {
o.childNodes.push(this.childNodes[i].serialise());
}
return o;
};
MyTreeNode.prototype.toJSON = function () {
return JSON.stringify(this.serialise());
};
MyTreeNode.deserialise = function (o) {
var p = new MyTreeNode(o.nodeName), i;
for (i = 0; i < o.childNodes.length; ++i) {
p.addChild(MyTreeNode.deserialise(o.childNodes[i]));
}
return p;
};
MyTreeNode.parseJSON = function (str) {
var o = JSON.parse(str);
return MyTreeNode.deserialise(o);
};
return MyTreeNode;
}());
You could traverse your object and add parent properties to every subobject:
function addParents(obj) {
var name;
for (name in obj) {
if (typeof obj[name] === "object") {
addParents(obj[name]);
obj[name].parent = obj;
}
}
}
var obj = {
g: {
k: [
{
r : 1
},
{
r : 1
}
],
j: {
h: 1
}
}
};
addParents(obj);
console.log(obj.g.parent === obj); //true
console.log(obj.g.k.parent === obj.g); //true
console.log(obj.g.k[1].parent === obj.g.k); //true
console.log(obj.g.j.parent === obj.g); //true
And if you want to add objects later on, you could use something like this:
function addChild(obj, child, name){
obj[name] = child;
child.parent = obj;
}
addChild(obj.g, {t:1}, "xy");
console.log(obj.g.xy.parent === obj.g); //true
FIDDLE
I am trying to implement my own getElementById() function in Javascript. My idea/algorithm goes like this:
function myGetElemById(id){
// rootNode I suppose will be the BODY tag.
rootElem = get elements by TAGNAME (rootNode);
elems = rootElems.getChildren();
for(i=0; i<elems.length; i++){
if(!elems[i].hasChildren()){
myGetElemById(elems[i]);
} else {
if(elems[i].id == id)
return elems[i];
else
return null;
}
}
}
Method 1:
function myGetElemById(id){
return document.getElementById(id);
}
Method 2:
function myGetElemById(id){
return window[id];
}
Method 3: (newer browsers)
function myGetElemById(id){
return document.querySelectorAll('#' + id);
}
DONE!
Okay, seriously:
function getById(id, parent, list){
parent = parent || document.body;
list = list || [];
var l, child, children = parent.children;
if(children){
l = children.length;
while(l--){
child = children[l];
if(child.id == id) list.push(child);
getById(id, child, list);
}
}
return list;
}
Check out this feature and maybe you can get ideas
function getElementsStartsWithId( id ) {
var children = document.body.getElementsByTagName('*');
var elements = [], child;
for (var i = 0, length = children.length; i < length; i++) {
child = children[i];
if (child.id.substr(0, id.length) == id)
elements.push(child);
}
return elements;
}
first, you must deal with the elements has children, call myGetElemById() and choose to return or not to return, depend on the result. like this
...
if(!elems[i].hasChildren()){
var result = myGetElemById(elems[i]);
if (result != null)
return result;
} else {
...
second why iterate over all the elements of the dom? the native function is much more faster.
Custom get element by ID method using BFS:
function myGetElementById(id, root){
let queue = []; // Using array here but linkedList is more performant in both time and space complexity
queue.push(root);
let currentNode;
while(queue.length){
currentNode = queue.shift();
if(currentNode.id === id){
return currentNode;
}
queue.push(...currentNode.children);
}
return false;
}
I have a function in javascript which can write the output to the console but it is unable to return the value..
fetchData: function(dateToFetch){
if (mP.viewMode == 1){
$.each(mealData.DailymPs, function(k, item){
if( item.Date == formatDate(mP.chosenDate) ){
mP.DayPlan.mPDayData = item;
return mP.populateMealDayPlan();
}
})
} else if (mP.viewMode == 2){
// debugger;
$.each(mealData.DailymPs, function(k, item){
if( item.Date == (dateToFetch) ){
mP.DayPlan.mPDayData = item;
console.log(mP.populateMealDayPlan());
var returnObj = mP.populateMealDayPlan();
return returnObj;
}
})
}
}
You should be able to fix it by changing it from:
...
$.each(mealData.DailymPs, function(k, item){
if( item.Date == (dateToFetch) ){
mP.DayPlan.mPDayData = item;
console.log(mP.populateMealDayPlan());
var returnObj = mP.populateMealDayPlan();
return returnObj;
}
})
...
to
...
var returnObj = null;
$.each(mealData.DailymPs, function(k, item){
if( item.Date == (dateToFetch) ){
mP.DayPlan.mPDayData = item;
console.log(mP.populateMealDayPlan());
returnObj = mP.populateMealDayPlan();
return false; // break out of each()
}
})
if(returnObj != null) return returnObj;
...
Note: You'll also need to externalize the return variable in the if condition. I've demonstrated how that can be done for the else condition.
If you want to return something from a $.each loop, assign it to a variable in the outer scope, return false; to break the loop and then return it after the $.each(); call.
Without seeing mP.populateMealDayPlan, my first guess would be that calling the function twice to get the values changes some internal values of mP. In other words if I am right and you comment out the console.log, there should be a return value. You could also do:
var returnObj = mP.populateMealDayPlan();
console.log(returnObj);
rather than calling mP.populateMealDayPlan in the console.log
If this is not the case then I think we would need more context in order to help.
Your return statement returns from the function passed to the each method, not from your fetchData function. The grep function will be useful here:
fetchData: function(dateToFetch) {
var selector = mP.viewMode == 1 ? function(item) { return item.Date == formatDate(mP.chosenDate) } : function (item) { return item.Date == dateToFetch};
var matched = $.grep(mealData.DailymPs, selector);
if (matched.length) {
var item = matched[0];
mP.DayPlan.mPDayData = item;
console.log(mP.populateMealDayPlan());
return mP.populateMealDayPlan();
}
}