GLB
GLB

Reputation: 7

Getting return value for Array.forEach function

I'm stucked with the following function, trying to get back a value (a part of a dom tree).

Instead of receiving a useful value I just obtain a 0/undefined.

var findNodeForAttributeValue = function (node, innerXmlAttributeName, innerXmlAttributeValue) {
var indirectReturnVar='0';
if ((node.nodeType === 1)&&(node.hasAttribute(innerXmlAttributeName))) {
    if (node.getAttribute(innerXmlAttributeName)==innerXmlAttributeValue) {
        indirectReturnVar=node;
        return indirectReturnVar;
    }
}
if((node.hasChildNodes())&&(node.firstChild!=null)) {
    Array.forEach ( node.childNodes, function (children) {
        findNodeForAttributeValue(children, innerXmlAttributeName, innerXmlAttributeValue);
    } );
    return indirectReturnVar;
}

Edit

Updated code:

var findNodeForAttributeValue = function (node, innerXmlAttributeName, innerXmlAttributeValue) {
  var indirectReturnVar='0';
  if ((node.nodeType === 1) && (node.hasAttribute(innerXmlAttributeName))) {
    if (node.getAttribute(innerXmlAttributeName) == innerXmlAttributeValue) {
      indirectReturnVar = node;
      return indirectReturnVar;
    }
  }

  if ((node.hasChildNodes()) && (node.firstChild != null)) {
    for (var fi=0, fiLen=node.childNodes.length; fi<fiLen; fi++) {
      findNodeForAttributeValue(node.childNodes[fi], innerXmlAttributeName, innerXmlAttributeValue);
    }
    return indirectReturnVar;
  }
}

Upvotes: 0

Views: 11180

Answers (2)

Mulan
Mulan

Reputation: 135357

Below find1 takes a searching function f that will be called for the supplied node and once for each of the node's childNodes. When f returns true, the node is returned. Otherwise undefined is given which signals no result was found.

const find1 = (f, node, cursor = 0) =>
  node.nodeType === 1 && f (node)
    ? node
  : cursor === node.childNodes.length
    ? undefined
  : find1 (f, node.childNodes[cursor]) || find1 (f, node, cursor + 1)

console.log
  ( find1
      ( node => node.tagName === 'P'
      , document
      )
      // <p>first paragraph</p>
      
  , find1
      ( node => node.textContent === 'and a span'
      , document
      )
      // <span>and a span</span>
      
  , find1
      ( node => node.getAttribute('class') === 'last'
      , document
      )
      // <p class="last">last paragraph</p>
  )
<div id="main">
  <!-- comment -->
  <h1>title</h1>
  <p>first paragraph</p>
  <p>second paragraph <span>and a span</span></p>
  <p class="last">last paragraph</p>
<div>

Above find1 is not limited to searching for nodes by a specific attribute and value. Instead the user-supplied lambda allows the programmer to direct find1 toward its goal.


What if we want all the results of find? Below findAll returns an Array of all matched results

const findAll = (f, node) =>
{ const loop = function* (node)
  { if (node.nodeType === 1 && f (node))
      yield node
    for (const child of node.childNodes)
      yield* loop (child)
  }
  return Array.from (loop (node))
}

console.log
  ( findAll
      ( node => node.tagName === 'P'
      , document
      )
      // [ <p>first paragraph</p>
      // , <p>second paragraph<span>...</span></p>
      // , <p class="last">last paragraph</p>
      // ]
      
  , findAll
      ( node => node.getAttribute('class') === 'last'
      , document
      )
      // [ <p class="last">last paragraph</p> ]
  )
<div id="main">
  <!-- comment -->
  <h1>title</h1>
  <p>first paragraph</p>
  <p>second paragraph <span>and a span</span></p>
  <p class="last">last paragraph</p>
<div>


Higher-order functions like find1 and findAll are great because they can be specialized in all sorts of useful ways.

const findByTag = (tagName, node) =>
  findAll
    ( node => node.tagName === tagName.toUpperCase()
    , node
    )

const findByAttrValue = (attr, value, node) =>
  findAll
    ( node => node.getAttribute (attr) === value
    , node
    )

console.log
  ( findByTag ('p', document)
    // [ '<p>...</p>', '<p>...</p>', '<p>...</p>' ]

  , findByTag ('h1', document)
    // [ '<h1>title</h1>' ]

  , findByTag ('strong', document)
    // []

  , findByAttrValue ('class', 'last', document)
    // [ <p class="last">last paragraph</p> ]

  , findByAttrValue ('id', 'main', document)
    // [ <div id="main">...</div> ]

  , findByAttrValue ('class', 'first', document)
    // []
  )

Upvotes: 0

RobG
RobG

Reputation: 147423

When you do:

> Array.forEach ( node.childNodes .. )

forEach is a method of Array instances that is on Array.prototype. the childNodes property is a NodeList, which is not an Array.

In some browsers that support ES5 you can do:

Array.prototype.forEach.call(childNodes, ...)

but that isn't guaranteed to work (and will fail in IE 8 and lower). So just use a for loop:

for (var i=0, iLen=node.childNodes.length; i<iLen; i++) {
  // do stuff with node.childNodes[i];
}

Edit

To fix your updated code:

function findNodeForAttributeValue (node, innerXmlAttributeName, innerXmlAttributeValue) {

Use a function declaration, I don't understand why you are using expressions with assignment. Also, shorter variable names will make life a lot easier, I'd probably do something like:

function getNodeByAttributeValue (node, att, value)

If you want a variable to have a truthy value, just set it to true. In this case, you want it falsey so either leave it undefined or set it to null (since most DOM methods return null if they don't get a matching element):

  var indirectReturnVar = null;

This for block is fine.

  if ((node.nodeType === 1) && (node.hasAttribute(innerXmlAttributeName))) {

    if (node.getAttribute(innerXmlAttributeName) == innerXmlAttributeValue) {
      indirectReturnVar = node;
      return indirectReturnVar;
    }
  }

  if ((node.hasChildNodes()) && (node.firstChild != null)) {

This bit needs modifying. Only keep looping while indirectReturnVar is falsey:

    for (var fi=0, fiLen=node.childNodes.length; fi<fiLen && !indirectReturnVar; fi++) {

Assign the returned value of the recursive function to indirectReturnVar, otherwise it gets lost in the ether.

      indirectReturnVar = findNodeForAttributeValue(node.childNodes[fi], innerXmlAttributeName, innerXmlAttributeValue);


    }
  }

Return the value outside the recursive loop. It will only loop until it either finds a matching node or runs out of nodes.

  return indirectReturnVar;
}

Upvotes: 1

Related Questions