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.


<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>

Open in new window


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.
LVL 6
hyphenpipeAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Jeffrey Dake Senior Director of TechnologyCommented:
Something like the following should work.  Just keep grabbing the elements until the data attribue is no longer present.  That lets you know when to exit out.  Not sure what you wanted returned, so the example has an array.

$(#menu SPAN).on("click", function(e)
{
  var dataArray = [];
  var data = $(this).attr('data-parent');
  while (data != null && data != undefined)
  {
     dataArray.push(data);
     var parentElement = $(data);
     if (parentElement != null && parentElement != undefined)
         data = parentElement .attr('data-parent');
  }

  return dataArray;
}

Open in new window

0
hyphenpipeAuthor Commented:
Can this be done without jQuery?  Pure javascript?
0
leakim971PluritechnicianCommented:
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>

Open in new window

0
Determine the Perfect Price for Your IT Services

Do you wonder if your IT business is truly profitable or if you should raise your prices? Learn how to calculate your overhead burden with our free interactive tool and use it to determine the right price for your IT services. Download your free eBook now!

hyphenpipeAuthor Commented:
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.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
If you want an option that does not use jQuery, here's a pure javascript method that also does not require recursion.

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();

Open in new window


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.getElementByTagName 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.
0
leakim971PluritechnicianCommented:
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>

Open in new window

0
hyphenpipeAuthor Commented:
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.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
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
        };
    }
}

Open in new window

0
Brian DayhoffSenior Full Stack DeveloperCommented:
Here's a working fiddle for you.

https://jsfiddle.net/mopsyd/x7mqrw8b/
0
hyphenpipeAuthor Commented:
Brian Dayhoff, that link looks to be incomplete.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
Jsfiddle didn't save my revision. Check again, it should be all set.
0
Julian HansenCommented:
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.
<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>

Open in new window

You can see it working here
0
hyphenpipeAuthor Commented:
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:

<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>

Open in new window


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.
0
Julian HansenCommented:
Did you see my code and sample - it does what you are asking for.

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

Open in new window

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
0
hyphenpipeAuthor Commented:
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)
0
Julian HansenCommented:
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.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
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:

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 = [];
    }
}

Open in new window


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.
0
Julian HansenCommented:
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.
0
hyphenpipeAuthor Commented:
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?
0
hyphenpipeAuthor Commented:
Julian,

Console reports:

Uncaught ReferenceError: $ is not defined
    at t3122.html:71
0
Brian DayhoffSenior Full Stack DeveloperCommented:
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.
0
hyphenpipeAuthor Commented:
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.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
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.
0
hyphenpipeAuthor Commented:
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
0
Julian HansenCommented:
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>

Open in new window

0
Brian DayhoffSenior Full Stack DeveloperCommented:
Do you need to track the id also? That is not incredibly hard, but you need to frame your question clearly to reflect that.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
"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.
0
hyphenpipeAuthor Commented:
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?
0
Brian DayhoffSenior Full Stack DeveloperCommented:
Here's the mixup. You asked for ancestors. This is the markup you provided for one:

<div id="one" class="expanded highlight">One<span></span></div>

Open in new window


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.
0
hyphenpipeAuthor Commented:
I amended my request in a reply to indicate I need all ancestors and descendants.

Perhaps I should delete this question and repost it?
0
leakim971PluritechnicianCommented:
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>

Open in new window

0
Brian DayhoffSenior Full Stack DeveloperCommented:
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.

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 = [];
    }
}

Open in new window


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.
0
Brian DayhoffSenior Full Stack DeveloperCommented:
If that is what you are going for, say so and I'll update the fiddle again for you.
0
Julian HansenCommented:
The issue was caused by a scoping problem on the getChildren function. The recursion was creating a new array each time due to the var children=[] being created in the global scope.

Here is the updated code
<script>
window.addEventListener('load', function() {
  var tree = buildTree('menu');
  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);
  console.log(relatives);

  while(node.parent) {
    relatives.push(node.parent);
    node = tree.index[node.parent];
  }
  
  
  return relatives;
}

/*
  Small change to get around scoping issue on children array
*/
function getChildren(node)
{
  var children = [];
  
  function recurse(node)
  {
	  for(var i in node) {
		if (i == 'parent') continue;
		children.push(i);
		var grandchildren = recurse(node[i])
		children.concat(grandchildren);
	  }
  }
  
  recurse(node);
  
  return children;
}
</script>

Open in new window

Updated link here
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Brian DayhoffSenior Full Stack DeveloperCommented:
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:

(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');

Open in new window


Fiddle: https://jsfiddle.net/mopsyd/x7mqrw8b/51/
0
hyphenpipeAuthor Commented:
Julian Hansen, this is what I needed.  I appreciate everyone's patience with this and working on it.  I can now sleep at night.
0
hyphenpipeAuthor Commented:
I appreciate everything from everyone. You are all stellar!!!
0
Julian HansenCommented:
You are welcome.
1
leakim971PluritechnicianCommented:
could you say what i missed in my last comment and solution which come before the accepted answer?
0
hyphenpipeAuthor Commented:
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.
1
leakim971PluritechnicianCommented:
no problem :D
1
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
CSS

From novice to tech pro — start learning today.