1

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>

0

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

this is what i have been missing :) perfect thank you, as soon as i can i will accept this answer, this solves the problem, i was missing the check for attempting
1

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

Comments

0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.