I am having hard time fixing my recursive function, which is a simplified tool for to scan DOM items and return matching element when found somewhere within document.
find: function(selector, element) {
if(selector !== undefined) {
if(element === undefined) {
element = this.e;
}
var elements = element.childNodes;
for(var i = 0; i < elements.length; i++) {
if(elements[i].nodeType === 1) {
console.log(elements[i]);
if(this.has_class(selector, elements[i]) === true) {
console.log('YAY, found it', elements[i]);
return elements[i];
} else {
if(elements[i].childNodes.length > 0) {
this.find(selector, elements[i]);
}
}
}
}
}
return false;
}
So the function should scan through children (and possibly their children) of given DOM element and return found element, otherwise go deeper and try again.
This is debuggable DEMO .
As you can see in logs, it triggered console.log('found'); but it did not leave function returning it, but continue and eventually returned false (as of not found). How can it be fixed?
var tools = { e: document.querySelector('.breadcrumbs'), has_class: function(name, element) { if (element.className === name) { return true; } return false; }, find: function(selector, element) { if (selector !== undefined) { if (element === undefined) { element = this.e; } var elements = element.childNodes; for (var i = 0; i < elements.length; i++) { if (elements[i].nodeType === 1) { console.log(elements[i]); if (this.has_class(selector, elements[i]) === true) { console.log('YAY, found it', elements[i]); return elements[i]; } else { if (elements[i].childNodes.length > 0) { this.find(selector, elements[i]); } } } } } return false; } }; console.log(tools.find('test'));
<div class="breadcrumbs" data-active="true"> <div class="bc_navigation" onclick="events.bc_toggle(event, this);"> <span class="bc_arrow"></span> </div> <div class="bc_content"> <div class="bc_wrapper"> <div class="step"> <span class="dot"></span><a onclick="events.go_home(event, this);">Landing</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-sport][1|0|0|0|0]">Soccer</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-group][1|4|0|0|0]">International</a> </div> <div class="step"> <span class="dot"></span><a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> </div> <div class="step"> <span class="dot"></span><a>Russia - Saudi Arabia</a> </div> </div> </div> </div>
The return
exits the call to find
where you found the element, but doesn't unwind all the calls that lead up to it.
Instead of
this.find(selector, elements[i]);
...in your else
, you need to see if you got the element and, if so, return:
var result = this.find(selector, elements[i]);
if (result) {
return result;
}
That lets it propagate up the chain.
Updated Live Example:
var tools = { e: document.querySelector('.breadcrumbs'), has_class: function(name, element) { if (element.className === name) { return true; } return false; }, find: function(selector, element) { if (selector !== undefined) { if (element === undefined) { element = this.e; } var elements = element.childNodes; for (var i = 0; i < elements.length; i++) { if (elements[i].nodeType === 1) { console.log(elements[i]); if (this.has_class(selector, elements[i]) === true) { console.log('YAY, found it', elements[i]); return elements[i]; } else { if (elements[i].childNodes.length > 0) { var result = this.find(selector, elements[i]); if (result) { return result; } } } } } } return false; } }; console.log(tools.find('test'));
<div class="breadcrumbs" data-active="true"> <div class="bc_navigation" onclick="events.bc_toggle(event, this);"> <span class="bc_arrow"></span> </div> <div class="bc_content"> <div class="bc_wrapper"> <div class="step"> <span class="dot"></span><a onclick="events.go_home(event, this);">Landing</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-sport][1|0|0|0|0]">Soccer</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-group][1|4|0|0|0]">International</a> </div> <div class="step"> <span class="dot"></span><a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> </div> <div class="step"> <span class="dot"></span><a>Russia - Saudi Arabia</a> </div> </div> </div> </div>
This is one of the key things about recursive functions: When they call themselves, they must look at the result of that call and propagate it when appropriate.
Result of the recursive call to find
isn't processed. You should check to result value of the recursive call and should return its value when the recursive call located the element:
find: function(selector, element) {
if(selector !== undefined) {
if(element === undefined) {
element = this.e;
}
var elements = element.childNodes;
for(var i = 0; i < elements.length; i++) {
if(elements[i].nodeType === 1) {
console.log(elements[i]);
if(this.has_class(selector, elements[i]) === true) {
console.log('YAY, found it', elements[i]);
return elements[i];
} else {
var foundElement = this.find(selector, elements[i]);
// *** Added this check to return the located element from the recursive call
if (foundElement != false) {
return foundElement;
}
}
}
}
}
return false;
}
querySelector potential wasted
Your tools
library of functions is a good effort, but it shows a lack of understanding of how querySelector
actually works. To demonstrate my point, your entire program is rewritten below
// starting with the Document element, get the first child matching '.breadcrumbs'
const elem =
document.querySelector ('.breadcrumbs')
// starting with `elem`, get the first child matching '.test'
const child =
elem.querySelector ('.test')
console.log (child)
// <a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a>
const elem = document.querySelector ('.breadcrumbs') const someChild = elem.querySelector ('.test') console.log (someChild) // <a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a>
<div class="breadcrumbs" data-active="true"> <div class="bc_navigation" onclick="events.bc_toggle(event, this);"> <span class="bc_arrow"></span> </div> <div class="bc_content"> <div class="bc_wrapper"> <div class="step"> <span class="dot"></span><a onclick="events.go_home(event, this);">Landing</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-sport][1|0|0|0|0]">Soccer</a> </div> <div class="step"> <span class="dot"></span><a href="#prematch[prematch-group][1|4|0|0|0]">International</a> </div> <div class="step"> <span class="dot"></span><a class="test" href="#prematch[prematch-event][1|4|16|10|0]">Int - World Cup</a> </div> <div class="step"> <span class="dot"></span><a>Russia - Saudi Arabia</a> </div> </div> </div> </div>
multiple classes
Above, querySelector
already does everything we need it to, but your tools.has_class
exhibits another deficiency – elements can have more than one class. Your function would've skipped over a child that had an attribute class="test foo"
.
For the sake of discussion, if you had to implement this on your own, you could adapt your has_class
function to separate the element's classes by space, then check each class for a match – or you could use Element.classList
which already includes a contains
function
const elem = document.querySelector ('.test') console.log (elem.classList) // { DOMTokenList [ "foo", "test", "bar" ] } console.log (elem.classList.contains ('foo')) // true console.log (elem.classList.contains ('test')) // true console.log (elem.classList.contains ('dog')) // false
<div class="foo test bar"></div>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.