hyphenpipe
asked on
I am stuck in what I can only assume is recursion hell.
Given the following HTML snippet, of which the structure cannot change, when I click on a span, I need to know the parent divs id, the parent divs parent div id and all of the ancestors based on the div ids and data-parent.
For example, if I click on the div who's ID is one, I would get the ID's of every other div.
If I click on the div who's ID is thirteen it should return the ID's of divs fourteen, twelve, ten, seven, and one.
I need to accomplish this with pure javascript.
<div id="menu">
<div id="one" class="expanded highlight">One<span></span></div>
<div id="two" data-parent="one" class="">Two<span></span></div>
<div id="three" data-parent="two" class="collapsed">Three</div>
<div id="four" data-parent="two" class="collapsed">Four</div>
<div id="five" data-parent="two" class="collapsed">Five</div>
<div id="six" data-parent="two" class="collapsed">Six</div>
<div id="seven" data-parent="one" class="">Seven<span></span></div>
<div id="eight" data-parent="seven" class="collapsed">Eight</div>
<div id="nine" data-parent="seven" class="collapsed">Nine</div>
<div id="ten" data-parent="seven" class="collapsed">Ten<span></span></div>
<div id="eleven" data-parent="ten" class="collapsed">Eleven</div>
<div id="twelve" data-parent="ten" class="collapsed">Twelve<span></span></div>
<div id="thirteen" data-parent="twelve" class="collapsed">Thirteen<span></span></div>
<div id="fourteen" data-parent="thirteen" class="collapsed">Fourteen</div>
<div id="fifteen" data-parent="twelve" class="collapsed">Fifteen</div>
<div id="sixteen" data-parent="twelve" class="collapsed">Sixteen</div>
<div id="seventeen" data-parent="ten" class="collapsed">Seventeen</div>
<div id="eighteen" data-parent="ten" class="collapsed">Eighteen</div>
<div id="nineteen" data-parent="one" class="">Nineteen<span></span></div>
<div id="twenty" data-parent="nineteen" class="collapsed">Twenty</div>
<div id="twentyone" data-parent="one" class="">Twentyone</div>
<div id="twentytwo" data-parent="one" class="">Twentytwo<span></span></div>
<div id="twentythree" data-parent="twentytwo" class="collapsed">Twentythree<span></span></div>
<div id="twentyfour" data-parent="twentythree" class="collapsed">Twentyfour<span></span></div>
<div id="twentyfive" data-parent="twentyfour" class="collapsed">Twentyfive</div>
<div id="twentysix">Twentysix</div>
<div id="twentyseven">Twentyseven</div>
<div id="twentyeight">Twentyeight</div>
<div id="twentynine">Twentynine</div>
</div>
For example, if I click on the div who's ID is one, I would get the ID's of every other div.
If I click on the div who's ID is thirteen it should return the ID's of divs fourteen, twelve, ten, seven, and one.
I need to accomplish this with pure javascript.
ASKER
Can this be done without jQuery? Pure javascript?
test page : https://jsfiddle.net/66ad4ruk/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>29096770</title>
</head>
<body>
<div id="menu">
<div id="one" class="expanded highlight">One<span></span></div>
<div id="two" data-parent="one" class="">Two<span></span></div>
<div id="three" data-parent="two" class="collapsed">Three</div>
<div id="four" data-parent="two" class="collapsed">Four</div>
<div id="five" data-parent="two" class="collapsed">Five</div>
<div id="six" data-parent="two" class="collapsed">Six</div>
<div id="seven" data-parent="one" class="">Seven<span></span></div>
<div id="eight" data-parent="seven" class="collapsed">Eight</div>
<div id="nine" data-parent="seven" class="collapsed">Nine</div>
<div id="ten" data-parent="seven" class="collapsed">Ten<span></span></div>
<div id="eleven" data-parent="ten" class="collapsed">Eleven</div>
<div id="twelve" data-parent="ten" class="collapsed">Twelve<span></span></div>
<div id="thirteen" data-parent="twelve" class="collapsed">Thirteen<span></span></div>
<div id="fourteen" data-parent="thirteen" class="collapsed">Fourteen</div>
<div id="fifteen" data-parent="twelve" class="collapsed">Fifteen</div>
<div id="sixteen" data-parent="twelve" class="collapsed">Sixteen</div>
<div id="seventeen" data-parent="ten" class="collapsed">Seventeen</div>
<div id="eighteen" data-parent="ten" class="collapsed">Eighteen</div>
<div id="nineteen" data-parent="one" class="">Nineteen<span></span></div>
<div id="twenty" data-parent="nineteen" class="collapsed">Twenty</div>
<div id="twentyone" data-parent="one" class="">Twentyone</div>
<div id="twentytwo" data-parent="one" class="">Twentytwo<span></span></div>
<div id="twentythree" data-parent="twentytwo" class="collapsed">Twentythree<span></span></div>
<div id="twentyfour" data-parent="twentythree" class="collapsed">Twentyfour<span></span></div>
<div id="twentyfive" data-parent="twentyfour" class="collapsed">Twentyfive</div>
<div id="twentysix">Twentysix</div>
<div id="twentyseven">Twentyseven</div>
<div id="twentyeight">Twentyeight</div>
<div id="twentynine">Twentynine</div>
</div>
<script>
var divs = document.getElementById("menu").getElementsByTagName("div");
for(var i=0;i<divs.length;i++) {
if(divs[i].dataset.parent || divs[i].getAttribute("id") == "one") {
divs[i].onclick = function() {
var arr = [];
var that = this;
while(true) {
_parent = that.dataset.parent;
if(!_parent)
break;
arr.push(_parent);
that = document.getElementById(_parent);
}
// do what you want with arr
// for example :
alert(arr.toString());
}
}
}
</script>
</body>
</html>
ASKER
leakim971, I not only need to know the parents, but all ancestors of the clicked element, based on the data-parent attribute.
If I click on thirteen, I expect to see fourteen (which is the child of thirteen) and twelve,ten,seven,one which your brilliant script does do.
If I click on thirteen, I expect to see fourteen (which is the child of thirteen) and twelve,ten,seven,one which your brilliant script does do.
If you want an option that does not use jQuery, here's a pure javascript method that also does not require recursion.
If you want fast, performant dom navigation that is simple to read and not complicated to implement without having to include any third party libraries, then you need to make document.querySelector your best buddy.
It will take pretty much any identical selector that jQuery will take, saving you the pain of working with document.getElementByTagNa me or document.getElementById (although those are both marginally faster). It also contains a reference to its parent, children, next and previous siblings, which lets you navigate to basically anywhere on the dom tree as needed.
Read and be enlightened:
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
If you want a group instead of a single element, use document.querySelectorAll instead. Syntax is the same, except it returns a collection instead of a single element.
var menu = document.querySelector('#menu'); // the starting element
var current_element = menu; // the iteration element
var elements = []; // your collection of elements
var data; // working state data
while ( current_element.nodeName !== "BODY" && current_element.nodeName !== "body" && typeof current_element.parentNode !== null ) // some browsers cast this to upper case, some do not
{
// The querySelector always contains a reference to its parent, children, next and previous siblings. Just create separate iteration to navigate the dom from this to grab whatever you need.
data = {
"data-parent": current_element.attributes['data-parent'].nodeValue,
"parent": current_element.parentNode.attributes.id.nodeValue,
"parent-parent": current_element.parentNode.parentNode.attributes.id.nodeValue
};
elements.push(data);
current_element = current_element.parentNode;
}
console.groupCollapsed('Profit. (click to expand)');
console.dir( elements );
console.groupEnd();
If you want fast, performant dom navigation that is simple to read and not complicated to implement without having to include any third party libraries, then you need to make document.querySelector your best buddy.
It will take pretty much any identical selector that jQuery will take, saving you the pain of working with document.getElementByTagNa
Read and be enlightened:
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
If you want a group instead of a single element, use document.querySelectorAll instead. Syntax is the same, except it returns a collection instead of a single element.
here we go : https://jsfiddle.net/66ad4ruk/1/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>29096770</title>
</head>
<body>
<div id="menu">
<div id="one" class="expanded highlight">One<span></span></div>
<div id="two" data-parent="one" class="">Two<span></span></div>
<div id="three" data-parent="two" class="collapsed">Three</div>
<div id="four" data-parent="two" class="collapsed">Four</div>
<div id="five" data-parent="two" class="collapsed">Five</div>
<div id="six" data-parent="two" class="collapsed">Six</div>
<div id="seven" data-parent="one" class="">Seven<span></span></div>
<div id="eight" data-parent="seven" class="collapsed">Eight</div>
<div id="nine" data-parent="seven" class="collapsed">Nine</div>
<div id="ten" data-parent="seven" class="collapsed">Ten<span></span></div>
<div id="eleven" data-parent="ten" class="collapsed">Eleven</div>
<div id="twelve" data-parent="ten" class="collapsed">Twelve<span></span></div>
<div id="thirteen" data-parent="twelve" class="collapsed">Thirteen<span></span></div>
<div id="fourteen" data-parent="thirteen" class="collapsed">Fourteen</div>
<div id="fifteen" data-parent="twelve" class="collapsed">Fifteen</div>
<div id="sixteen" data-parent="twelve" class="collapsed">Sixteen</div>
<div id="seventeen" data-parent="ten" class="collapsed">Seventeen</div>
<div id="eighteen" data-parent="ten" class="collapsed">Eighteen</div>
<div id="nineteen" data-parent="one" class="">Nineteen<span></span></div>
<div id="twenty" data-parent="nineteen" class="collapsed">Twenty</div>
<div id="twentyone" data-parent="one" class="">Twentyone</div>
<div id="twentytwo" data-parent="one" class="">Twentytwo<span></span></div>
<div id="twentythree" data-parent="twentytwo" class="collapsed">Twentythree<span></span></div>
<div id="twentyfour" data-parent="twentythree" class="collapsed">Twentyfour<span></span></div>
<div id="twentyfive" data-parent="twentyfour" class="collapsed">Twentyfive</div>
<div id="twentysix">Twentysix</div>
<div id="twentyseven">Twentyseven</div>
<div id="twentyeight">Twentyeight</div>
<div id="twentynine">Twentynine</div>
</div>
<script>
var divs = document.getElementById("menu").getElementsByTagName("div");
for(var i=0;i<divs.length;i++) {
if(divs[i].dataset.parent || divs[i].getAttribute("id") == "one") {
divs[i].onclick = function() {
var arr = [];
var that = this;
while(true) {
_parent = that.dataset.parent;
if(!_parent)
break;
arr.push(_parent);
that = document.getElementById(_parent);
}
var divs = document.getElementById("menu").getElementsByTagName("div");
for(var k=0;k<divs.length;k++) {
if(divs[k].dataset.parent == this.getAttribute("id"))
arr.push(divs[k].getAttribute("id"));
}
// do what you want with arr
// for example :
alert(arr.toString());
}
}
}
</script>
</body>
</html>
ASKER
leakim971, this is close.
Take a look at this:
https://jsfiddle.net/66ad4ruk/5/
When I click on twentytwo, I was hoping the array elements would be [one, twentythree, twentyfour, twentyfive]
twentyfour and twentyfive are ancestors of twentytwo based on the data-parent chain.
seven also has a lot of ancestors.
Take a look at this:
https://jsfiddle.net/66ad4ruk/5/
When I click on twentytwo, I was hoping the array elements would be [one, twentythree, twentyfour, twentyfive]
twentyfour and twentyfive are ancestors of twentytwo based on the data-parent chain.
seven also has a lot of ancestors.
In your case, where all of the elements determine inheritance within one chunk of menu children through entirely non-standard dom inheritance, you would instead do something like this:
var root = document.querySelector('#menu');
var the_click_data = {};
for (let element in root)
{
element.onclick = function() {
var inheritance = [];
var output = [];
inheritance.push(element);
var current_element = element;
while ( current_element.attributes['data-parent'] !== undefined )
{
inheritance.push( current_element );
output.push(current_element.attributes.id.nodeValue);
current_element = document.querySelector( '#'+element.attributes['data-parent'].nodeValue );
current_element = current_element.parentNode;
}
console.groupCollapsed(' The Whole Shebang of confusing id relations');
console.info( 'The ids only, in reverse order from clicked child to origin.' );
console.dir( output );
console.info( 'The query selector objects for each subsequent element backwards, in the same order' );
console.dir( inheritance );
console.groupEnd();
// And just in case you had to work with this information further:
return {
"ids": output,
"selectors": inheritance
};
}
}
ASKER
Brian Dayhoff, that link looks to be incomplete.
Jsfiddle didn't save my revision. Check again, it should be all set.
Here is a solution that is based on building an indexed tree. Once built we can re-use it to find all parents and descendants
In your earlier posts you spoke of all Ancestors and then referred to returning 14 when 13 is clicked - did you mean descendants? I have assumed this is what you meant.
In the solution below we first iterate over all the items and build both an index (of where an item is in the tree - by it's id) and a tree that represents the hierarchical nature of the data.
When an item is clicked we use the item's id to find it in the index. From there we can traverse up the tree (single parent at a time) or recursively gather grandchildren (going down the tree).
The solution has a main function to return relatives which first calls a recursive function to find children / grandchildren, This forms the base return array of values. To this we add the ancestors by going up the tree by parent and adding those values to the array.
In your earlier posts you spoke of all Ancestors and then referred to returning 14 when 13 is clicked - did you mean descendants? I have assumed this is what you meant.
In the solution below we first iterate over all the items and build both an index (of where an item is in the tree - by it's id) and a tree that represents the hierarchical nature of the data.
When an item is clicked we use the item's id to find it in the index. From there we can traverse up the tree (single parent at a time) or recursively gather grandchildren (going down the tree).
The solution has a main function to return relatives which first calls a recursive function to find children / grandchildren, This forms the base return array of values. To this we add the ancestors by going up the tree by parent and adding those values to the array.
<script>
$(function() {
var tree = buildTree('menu');
var child = document.getElementById('menu').getElementsByTagName('div');
for(var i = 0; i < child.length; i++) {
child[i].addEventListener('click', function(e) {
var relatives = getRelatives(tree, this);
console.log(relatives);
})
}
});
// Build an indexed tree
function buildTree(id)
{
var elements = document.getElementById(id).getElementsByTagName('div');
var tree = {
nodes: {},
index: []
};
for(var i = 0; i < elements.length; i++) {
var node = elements[i];
var parent = node.dataset.parent || false;
if (!parent) {
var nn = {
parent: parent
};
tree.nodes[node.id] = nn;
tree.index[node.id] = nn;
}
else {
var nn = {
parent: node.dataset.parent
};
tree.index[node.dataset.parent][node.id] = nn;
tree.index[node.id] = nn;
}
}
return tree;
}
function getRelatives(tree, item)
{
var node = tree.index[item.id];
var relatives = getChildren(node);
while(node.parent) {
relatives.push(node.parent);
node = tree.index[node.parent];
}
return relatives;
}
function getChildren(node)
{
var children = [];
for(var i in node) {
if (i == 'parent') continue;
children.push(i);
var grandchildren = getChildren(node[i])
children.concat(grandchildren);
}
return children;
}
</script>
You can see it working here
ASKER
Julian Hansen, I did mean descendants. I fear my making that mistake in nomenclature has muddied the waters quite a bit and I apologize.
I need to know all ancestors and descendants of the clicked element based on the data-parent attribute.
A perfect example is this section of my code here:
If I click on 'seven', I should see all of the items in this list as well as 'one' because 'one' is 'sevens' parent and all the others are descendants of 'seven' based on the data-parent chain.
Seven is a parent of eight. nine, and ten. Ten is a parent of eleven and twelve, and ten is a child of seven, so I need to see this is an array or object.
Thirteen is a child of twelve which is a child of ten which is a child of seven so this goes into the array.
Fourteen is a child of thirteen which is a child of twelve which is a child of ten which is a child of seven so this goes into the array.
Conversely, if I click on thirteen, my expected items in the array would be [fourteen (the descendant), twelve, ten, seven, one (the ancestors)]
If fourteen had a child it would go into the array and if fourteens child had a child, it would go into the array, and so on and so forth.
I appreciate everyone's help with this. I hope I am explaining this correctly. I have been banging my head against the wall with this for the last couple of days. This is tougher than I imagined and still looking for a solution.
I need to know all ancestors and descendants of the clicked element based on the data-parent attribute.
A perfect example is this section of my code here:
<div id="seven" data-parent="one" class="">Seven<span></span></div>
<div id="eight" data-parent="seven" class="collapsed">Eight</div>
<div id="nine" data-parent="seven" class="collapsed">Nine</div>
<div id="ten" data-parent="seven" class="collapsed">Ten<span></span></div>
<div id="eleven" data-parent="ten" class="collapsed">Eleven</div>
<div id="twelve" data-parent="ten" class="collapsed">Twelve<span></span></div>
<div id="thirteen" data-parent="twelve" class="collapsed">Thirteen<span></span></div>
<div id="fourteen" data-parent="thirteen" class="collapsed">Fourteen</div>
<div id="fifteen" data-parent="twelve" class="collapsed">Fifteen</div>
<div id="sixteen" data-parent="twelve" class="collapsed">Sixteen</div>
<div id="seventeen" data-parent="ten" class="collapsed">Seventeen</div>
<div id="eighteen" data-parent="ten" class="collapsed">Eighteen</div>
If I click on 'seven', I should see all of the items in this list as well as 'one' because 'one' is 'sevens' parent and all the others are descendants of 'seven' based on the data-parent chain.
Seven is a parent of eight. nine, and ten. Ten is a parent of eleven and twelve, and ten is a child of seven, so I need to see this is an array or object.
Thirteen is a child of twelve which is a child of ten which is a child of seven so this goes into the array.
Fourteen is a child of thirteen which is a child of twelve which is a child of ten which is a child of seven so this goes into the array.
Conversely, if I click on thirteen, my expected items in the array would be [fourteen (the descendant), twelve, ten, seven, one (the ancestors)]
If fourteen had a child it would go into the array and if fourteens child had a child, it would go into the array, and so on and so forth.
I appreciate everyone's help with this. I hope I am explaining this correctly. I have been banging my head against the wall with this for the last couple of days. This is tougher than I imagined and still looking for a solution.
Did you see my code and sample - it does what you are asking for.
The code arranges the elements in a tree
It will also go recursively down the tree finding all children and grandchildren
It combines these values into the result array.
Click the link above, open the console and click Thirteen to see the results.
Link reposted here http://www.marcorpsa.com/ee/t3122.html
The code arranges the elements in a tree
one
nineteen
seven
eight
nine
ten
eighteen
eleven
seventeen
twelve
fifteen
sixteen
thirteen
fourteen
twentyone
twentytwo
two
When you click on Thirteen the code will go up the tree finding each parent.It will also go recursively down the tree finding all children and grandchildren
It combines these values into the result array.
Click the link above, open the console and click Thirteen to see the results.
Link reposted here http://www.marcorpsa.com/ee/t3122.html
ASKER
Julian,
When I click on seven, the array returned is:
[
"eight",
"nine",
"ten",
"one"
]
When I click on thirteen, the array is:
[
"fourteen",
"twelve",
"ten",
"seven",
"one"
]
I do now see the output you referenced. And in addition, can this be done with pure javascript? (no jQuery)
When I click on seven, the array returned is:
[
"eight",
"nine",
"ten",
"one"
]
When I click on thirteen, the array is:
[
"fourteen",
"twelve",
"ten",
"seven",
"one"
]
I do now see the output you referenced. And in addition, can this be done with pure javascript? (no jQuery)
There is no jQuery in that solution. The Code tab says jQuery because I used a standard boilerplate I use for samples and I did not update the label. I have done so. I have also removed the jQuery library from the page to demonstrate that the code is not dependent on jQuery.
Everything in that solution is pure JavaScript - as you requested in your original post.
Everything in that solution is pure JavaScript - as you requested in your original post.
The fiddle I had posted earlier solves this in pure javascript, and is agnostic enough to work if the structure changes also, so for example if you were to shuffle the data-parent attributes, or add new ones, or what have you, it would still work fine. The code for reference:
The fiddle link again: https://jsfiddle.net/mopsyd/x7mqrw8b/
This solution uses the native dom tree as it's registry, and does not incur any overhead of manually parsing out a new one. The dom is already a registry of elements.
let root = document.querySelector('#menu');
let data = [];
for (let element of root.childNodes)
{
element.onclick = function()
{
let current_element = element;
do
{
data.push(current_element.innerText);
if (typeof current_element.attributes['data-parent'] !== "undefined")
{
current_element = document.querySelector('#'+current_element.attributes['data-parent'].nodeValue);
}
} while ( typeof current_element.attributes['data-parent'] !== "undefined");
if ( !( data.includes( current_element.innerText ) ) )
{
// Catches oddballs that don't have a data attribute.
// This will work even if you add additional ones,
// alter the markup to try to break it, etc.
data.push(current_element.innerText);
}
alert(data.toString());
data = [];
}
}
The fiddle link again: https://jsfiddle.net/mopsyd/x7mqrw8b/
This solution uses the native dom tree as it's registry, and does not incur any overhead of manually parsing out a new one. The dom is already a registry of elements.
The dom is already a registry of elements.... but in this case that registry does not match the virtual structure imposed by the ordering of the data-parent attribute - there is no logical link between the value in data-parent and the layout in the DOM.
Creating a virtual hierarchical representation of the data makes for much easier data extraction and the overhead for creating it is minimal. In addition binding the logic to the onclick handler means that the search has to be done on each and every click - whereas building an indexed tree means that once the tree is built, each click results in a very fast data retrieval.
ASKER
I must be missing something. When I click on seven in your jsfiddle, I only see see Seven,One.
This can't be right. What am I missing?
This can't be right. What am I missing?
ASKER
Julian,
Console reports:
Uncaught ReferenceError: $ is not defined
at t3122.html:71
Console reports:
Uncaught ReferenceError: $ is not defined
at t3122.html:71
There are a couple of things here that let this work as one function without recursion that is only 26 lines with comments.
- The innerText attribute of query selectors ignores dom elements, which means you don't need to account for the empty spans.
- The current element is replaced by a new query selector of the id corresponding to its data-parent attribute. As these already exist in the dom, and the first line establishes the set, there is no additional overhead to parse these. Recursion is not needed at all.
- The if check at the end catches tail-end inheritance that does not have a data-parent attribute, such as the #one element. If it is a single element like #twentyseven, then it would be caught in the do..while anyhow. The if check ignores elements that already exist in the inheritance chain, which also circumvents any possiblity of a neverending while loop.
- The innerText attribute of query selectors ignores dom elements, which means you don't need to account for the empty spans.
- The current element is replaced by a new query selector of the id corresponding to its data-parent attribute. As these already exist in the dom, and the first line establishes the set, there is no additional overhead to parse these. Recursion is not needed at all.
- The if check at the end catches tail-end inheritance that does not have a data-parent attribute, such as the #one element. If it is a single element like #twentyseven, then it would be caught in the do..while anyhow. The if check ignores elements that already exist in the inheritance chain, which also circumvents any possiblity of a neverending while loop.
ASKER
Brian, I am not seeing the correct solution on your jsfiddle. I am using this URL: https://jsfiddle.net/mopsyd/x7mqrw8b/
Click on seven. I would expect to see a lot more items in the array. Seven has a lot of descendants.
Click on seven. I would expect to see a lot more items in the array. Seven has a lot of descendants.
Seven only has one as its data parent. It is correct according to the provided markup.
You had asked for association based only on the data parent. Seven inherits from one, and one does not inherit from anything.
You had asked for association based only on the data parent. Seven inherits from one, and one does not inherit from anything.
ASKER
I apologize for my confusion.
I made a mistake on my original request and updated what I as looking for in this reply:
Look for the reply in this thread with ID: 42547386
I made a mistake on my original request and updated what I as looking for in this reply:
Look for the reply in this thread with ID: 42547386
The $ was for the onload - I have amended to use addEventListener
<script>
window.addEventListener('load', function() {
var tree = buildTree('menu');
// SHOW WHAT THE TREE AND INDEX LOOKS LIKE
console.log('Tree', tree);
var child = document.getElementById('menu').getElementsByTagName('div');
for(var i = 0; i < child.length; i++) {
child[i].addEventListener('click', function(e) {
var relatives = getRelatives(tree, this);
console.log(relatives);
})
}
});
// Build an indexed tree
function buildTree(id)
{
var elements = document.getElementById(id).getElementsByTagName('div');
var tree = {
nodes: {},
index: []
};
for(var i = 0; i < elements.length; i++) {
var node = elements[i];
var parent = node.dataset.parent || false;
if (!parent) {
var nn = {
parent: parent
};
tree.nodes[node.id] = nn;
tree.index[node.id] = nn;
}
else {
var nn = {
parent: node.dataset.parent
};
tree.index[node.dataset.parent][node.id] = nn;
tree.index[node.id] = nn;
}
}
return tree;
}
function getRelatives(tree, item)
{
var node = tree.index[item.id];
var relatives = getChildren(node);
while(node.parent) {
relatives.push(node.parent);
node = tree.index[node.parent];
}
return relatives;
}
function getChildren(node)
{
var children = [];
for(var i in node) {
if (i == 'parent') continue;
children.push(i);
var grandchildren = getChildren(node[i])
children.concat(grandchildren);
}
return children;
}
</script>
Do you need to track the id also? That is not incredibly hard, but you need to frame your question clearly to reflect that.
"Given the following HTML snippet, of which the structure cannot change, when I click on a span, I need to know the parent divs id, the parent divs parent div id and all of the ancestors based on the div ids and data-parent."
- You got that.
"For example, if I click on the div who's ID is one, I would get the ID's of every other div."
- One does not have ancestors. This is not congruent with your stated intent.
"If I click on the div who's ID is thirteen it should return the ID's of divs fourteen, twelve, ten, seven, and one."
- You got that.
"I need to accomplish this with pure javascript."
- You got that also.
- You got that.
"For example, if I click on the div who's ID is one, I would get the ID's of every other div."
- One does not have ancestors. This is not congruent with your stated intent.
"If I click on the div who's ID is thirteen it should return the ID's of divs fourteen, twelve, ten, seven, and one."
- You got that.
"I need to accomplish this with pure javascript."
- You got that also.
ASKER
Julian,
I still only get this array when I click on seven.
[
"eight",
"nine",
"ten",
"one"
]
I must be missing something or asking the question wrong. Where are all of sevens descendants. It''s children, grandchildren, great-grandchildren, great-great-grandchildren, etc?
I still only get this array when I click on seven.
[
"eight",
"nine",
"ten",
"one"
]
I must be missing something or asking the question wrong. Where are all of sevens descendants. It''s children, grandchildren, great-grandchildren, great-great-grandchildren,
Here's the mixup. You asked for ancestors. This is the markup you provided for one:
There is no data-parent attribute, so there are no ancestors. There are children, but you did not ask for the children, you asked for the ancestors only. One does not have anything higher up except for the menu itself.
<div id="one" class="expanded highlight">One<span></span></div>
There is no data-parent attribute, so there are no ancestors. There are children, but you did not ask for the children, you asked for the ancestors only. One does not have anything higher up except for the menu itself.
ASKER
I amended my request in a reply to indicate I need all ancestors and descendants.
Perhaps I should delete this question and repost it?
Perhaps I should delete this question and repost it?
Here with ancestors : https://jsfiddle.net/66ad4ruk/6/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>29096770</title>
</head>
<body>
<div id="menu">
<div id="one" class="expanded highlight">One<span></span></div>
<div id="two" data-parent="one" class="">Two<span></span></div>
<div id="three" data-parent="two" class="collapsed">Three</div>
<div id="four" data-parent="two" class="collapsed">Four</div>
<div id="five" data-parent="two" class="collapsed">Five</div>
<div id="six" data-parent="two" class="collapsed">Six</div>
<div id="seven" data-parent="one" class="">Seven<span></span></div>
<div id="eight" data-parent="seven" class="collapsed">Eight</div>
<div id="nine" data-parent="seven" class="collapsed">Nine</div>
<div id="ten" data-parent="seven" class="collapsed">Ten<span></span></div>
<div id="eleven" data-parent="ten" class="collapsed">Eleven</div>
<div id="twelve" data-parent="ten" class="collapsed">Twelve<span></span></div>
<div id="thirteen" data-parent="twelve" class="collapsed">Thirteen<span></span></div>
<div id="fourteen" data-parent="thirteen" class="collapsed">Fourteen</div>
<div id="fifteen" data-parent="twelve" class="collapsed">Fifteen</div>
<div id="sixteen" data-parent="twelve" class="collapsed">Sixteen</div>
<div id="seventeen" data-parent="ten" class="collapsed">Seventeen</div>
<div id="eighteen" data-parent="ten" class="collapsed">Eighteen</div>
<div id="nineteen" data-parent="one" class="">Nineteen<span></span></div>
<div id="twenty" data-parent="nineteen" class="collapsed">Twenty</div>
<div id="twentyone" data-parent="one" class="">Twentyone</div>
<div id="twentytwo" data-parent="one" class="">Twentytwo<span></span></div>
<div id="twentythree" data-parent="twentytwo" class="collapsed">Twentythree<span></span></div>
<div id="twentyfour" data-parent="twentythree" class="collapsed">Twentyfour<span></span></div>
<div id="twentyfive" data-parent="twentyfour" class="collapsed">Twentyfive</div>
<div id="twentysix">Twentysix</div>
<div id="twentyseven">Twentyseven</div>
<div id="twentyeight">Twentyeight</div>
<div id="twentynine">Twentynine</div>
</div>
<script>
var getMyAncestor = function(myId) {
var divs = document.getElementById("menu").getElementsByTagName("div");
for(var k=0;k<divs.length;k++) {
if(divs[k].dataset.parent == myId)
return divs[k].getAttribute("id");
}
return null;
}
var divs = document.getElementById("menu").getElementsByTagName("div");
for(var i=0;i<divs.length;i++) {
if(divs[i].dataset.parent || divs[i].getAttribute("id") == "one") {
divs[i].onclick = function() {
var arr = [];
var that = this;
while(true) {
_parent = that.dataset.parent;
if(!_parent)
break;
arr.push(_parent);
that = document.getElementById(_parent);
}
that = this;
while(true) {
_ancestor = getMyAncestor(that.getAttribute("id"));
if(!_ancestor)
break;
arr.push(_ancestor);
that = document.getElementById(_ancestor);
}
// do what you want with arr
// for example :
alert(arr.toString());
}
}
}
</script>
</body>
</html>
If you want the child inheritance also, I believe you are looking for something like this.
Fiddle: https://jsfiddle.net/mopsyd/x7mqrw8b/23/
Slight modification to the above to also collect child descendants. Still does not require recursion.
This tracks forward and backward from the clicked element.
If you need forward and backward from all parent elements, that will require recursion. However implementing that is really only a matter of breaking the outer while loop off into its own method that calls itself on each subsequent iteration. that is also only a slight modification from this.
Fiddle: https://jsfiddle.net/mopsyd/x7mqrw8b/23/
Slight modification to the above to also collect child descendants. Still does not require recursion.
let root = document.querySelector('#menu');
let data = [];
let children = [];
for (let element of root.childNodes)
{
element.onclick = function()
{
let current_element = element;
do
{
data.push(current_element.innerText);
if (typeof current_element.attributes['data-parent'] !== "undefined")
{
current_element = document.querySelector('#'+current_element.attributes['data-parent'].nodeValue);
}
let current_element_id = current_element.attributes.id.nodeValue;
let child_element = document.querySelector('[data-parent="'+current_element_id+'"]');
console.dir(child_element);
while (child_element !== null)
{
children.push(child_element.innerText);
current_element_id = child_element.attributes.id.nodeValue;
child_element = document.querySelector('[data-parent="'+current_element_id+'"]');
console.dir(child_element);
}
} while ( typeof current_element.attributes['data-parent'] !== "undefined");
if ( !( data.includes( current_element.innerText ) ) )
{
// Catches oddballs that don't have a data attribute.
// This will work even if you add additional ones,
// alter the markup to try to break it, etc.
data.push(current_element.innerText);
}
alert(JSON.stringify({
"parent-inheritance": data.toString(),
"child-inheritance": children.toString()
}, null, 2) );
data = [];
children = [];
}
}
This tracks forward and backward from the clicked element.
If you need forward and backward from all parent elements, that will require recursion. However implementing that is really only a matter of breaking the outer while loop off into its own method that calls itself on each subsequent iteration. that is also only a slight modification from this.
If that is what you are going for, say so and I'll update the fiddle again for you.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Ok, just to keep this from getting out of hand, here's an approach that grabs literally everything forward and backward, and all forward and backward steps of each forward and backward step, if that makes sense. It alerts it, but it's huge, so it also puts it in a console group so you can look at the full output without it getting truncated, and returns the value as well if you need to work with it elsewhere. This should definitely cover any possible thing you were looking to accomplish and then some.
Code:
Fiddle: https://jsfiddle.net/mopsyd/x7mqrw8b/51/
Code:
(function( root_element ){
let root = document.querySelector(root_element);
let lib = {
getValue(selector)
{
return selector.innerText;
},
getParent(selector)
{
if (typeof selector.attributes['data-parent'] === "undefined")
{
return false;
}
return document.querySelector( '#'+selector.attributes['data-parent'].nodeValue);
},
getChildren(selector)
{
return document.querySelectorAll('[data-parent="'+selector.attributes.id.nodeValue+'"]');
},
getForwardInheritance(selector)
{
let data = {};
let children = lib.getChildren(selector);
for (let child of children)
{
data[child.attributes.id.nodeValue] = {
value: lib.getValue(child),
children: lib.getForwardInheritance(child),
// parents: lib.getBackwardsInheritance(child)
};
}
if ( JSON.stringify(data) === JSON.stringify({}) )
{
return false;
}
return data;
},
getBackwardsInheritance(selector)
{
let data = {};
let parent = selector.attributes['data-parent'];
let parent_element;
if (typeof selector === "undefined")
{
return false;
}
parent_element = document.querySelector('#'+parent.nodeValue);
data.value = lib.getValue(parent_element);
data.children = lib.getForwardInheritance(parent_element);
if (typeof parent_element.attributes['data-parent'] !== "undefined" )
{
data.parents = lib.getBackwardsInheritance(parent_element);
} else
{
data.parents = false;
}
return data;
}
};
for (let element of root.childNodes)
{
element.onclick = function()
{
let data = {};
data.value = lib.getValue(element);
data.children = lib.getForwardInheritance(element);
if (typeof element.attributes['data-parent'] !== "undefined")
{
data.parents= lib.getBackwardsInheritance(document.querySelector('#'+element.attributes['data-parent'].nodeValue ));
} else
{
data.parents = false;
}
console.groupCollapsed('Data Yielded for ' + data.value);
console.dir(data);
console.groupEnd();
alert(JSON.stringify(data, null, 2));
return data;
}
};
})('#menu');
Fiddle: https://jsfiddle.net/mopsyd/x7mqrw8b/51/
ASKER
Julian Hansen, this is what I needed. I appreciate everyone's patience with this and working on it. I can now sleep at night.
ASKER
I appreciate everything from everyone. You are all stellar!!!
You are welcome.
could you say what i missed in my last comment and solution which come before the accepted answer?
ASKER
leakim971, my apologies, my original request was wrong, as I needed all relatives of the clicked element, ancestors and descendants. I amended my request as a reply so you probably didn't see it. I am truly sorry about that and I will make an effort to be more clear going forward.
no problem :D
Open in new window