3

I have a traverse object containing two methods up() and down(), the goals of these methods are to loop upward or downward through the html looking for the first occurrence of specific dataset-attribute and if found return that element.

The "specific" dataset-attribute is passed as a param into the methods, for example purposes we have data-up and data-down.

var o1 = traverse.up(e.target,'up');
var o2 = traverse.down(e.target,'down');

Now the traversing up method traverse.up(e.target,'up') works fine since the parentNode is a one-to-one relation to the element clicked (e.target) however my problem is when trying to traverse downwards since the element clicked (e.target) may have multiple children, I would need to loop through each child and it's children, etc... searching for the dataset-down attribute.

Question: Why isn't my traverse.down(e.target,'down') method return the first occurrence of an HTML element with the dataset-down attribute?

Here is the JSFiddle demo

//HTML

<body>
    <div id='black'>
        <div id='red' data-up='upwards'>
            <div id='blue'>
                <div id='green'>
                    <div id='yellow'></div>
                    <div id='royalblue' data-down='downwards'></div>
                    <div id='fuscia'></div>
                </div>
            </div>
        </div>  
    </div>
</body>

//JS

function init(){
    document.getElementById('black').addEventListener('click', handles);
}
function handles(e){
//  var o1 = traverse.up(e.target,'up');
    var o2 = traverse.down(e.target,'down');
    console.log(o2);
}
traverse={
    up:function(o,a){
        while(o.dataset[a] === undefined){
            if(o.parentNode.tagName === 'HTML') break;
            o = o.parentNode;
        }
        return o;
    },
    down:function(o,a){
        if(o.dataset[a] === undefined){
            if(o.children.length > 0){
                o.children.forEach((o)=>{
                    traverse.down(o,a);
                })
            }
            else console.log("DOES NOT HAVE CHILD");
        }
        else{
            //console.log(o) **this does return the correct element with the data-down attribute however the return statement below isn't returning it back to the original caller.
            return o;
        }
    }
};
NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
document.onreadystatechange=()=>(document.readyState === 'interactive') ? init() : null;
Jordan Davis
  • 1,485
  • 7
  • 21
  • 40
  • You should probably `return traverse.down(o,a);` Or figure out what to do if multiple child elements contain the data- attribute you're after. – James Mar 27 '16 at 00:19
  • @James just tried adding return in the forEach, still logs `undefined` – Jordan Davis Mar 27 '16 at 00:21
  • Yeah. Most of the children won't have it. So detect if any do, and return those ones. – James Mar 27 '16 at 00:22
  • Have you thought about using nextElementSibling? – Dexter Mar 27 '16 at 00:22
  • @James yea thats exactly what I'm doing, if it doesn't have the `dataset-attribute` check for children, if children loop again else do nothing, else if element does have `dataset-attribute` then `return o` – Jordan Davis Mar 27 '16 at 00:25
  • @Dexter yea I did think of using that but if you just run `forEach` over the children it's doing the same thing. – Jordan Davis Mar 27 '16 at 00:26
  • Except with nextElementSibling, it ignores text. – Dexter Mar 27 '16 at 00:28
  • Also, if you're not just doing this for learning experience, you could save yourself some time by not reinventing the wheel: https://api.jquery.com/has-attribute-selector/ – Dexter Mar 27 '16 at 00:29
  • @Dexter the `forEach` doesn't run over the text. – Jordan Davis Mar 27 '16 at 00:33
  • @JordanDavis element.children included text nodes. Just thinking of ways to speed it up. querySelectorAll is another option. – Dexter Mar 27 '16 at 00:36
  • @Dexter it's not simply getting the data-attribute by it's attribute selector, it's based on closest occurrence of that attribute from the element being clicked. I would just use `querySelector()` if that were the case, not jQuery – Jordan Davis Mar 27 '16 at 00:36

1 Answers1

1

Something like this:

if(o.children.length > 0){
    o.children.forEach((o)=>{
        var t = traverse.down(o,a);
        if (t) return t; // return the first element that matches. There might be more but we're ignoring them
    });
    // none were found
    return false;
}
James
  • 20,957
  • 5
  • 26
  • 41
  • If you add `console.log(o)` above the `return o;` in my original code the last else statement you can see that `o` is the html element containing the `data-down` attribute however doesn't return it for some reason. – Jordan Davis Mar 27 '16 at 00:52
  • I figured it out, we both were close we just didn't need to use the forEach loop the return was returning to the forEach function not the caller. – Jordan Davis Mar 28 '16 at 20:52
  • @JordanDavis Ugh - yes, I was ignoring that foreach! makes sense though glad you figured it out. – James Mar 28 '16 at 21:14