Link to home
Start Free TrialLog in
Avatar of hyphenpipe
hyphenpipeFlag for United States of America

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.


<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.
Avatar of Jeffrey Dake
Jeffrey Dake
Flag of United States of America image

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

Avatar of hyphenpipe

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>

Open in new window

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

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

Here's a working fiddle for you.

https://jsfiddle.net/mopsyd/x7mqrw8b/
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.
<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
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.
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
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)
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.
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.
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.
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?
Julian,

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

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

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>

Open in new window

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.
If that is what you are going for, say so and I'll update the fiddle again for you.
ASKER CERTIFIED SOLUTION
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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/
Julian Hansen, this is what I needed.  I appreciate everyone's patience with this and working on it.  I can now sleep at night.
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?
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