Reputation: 7
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;
}
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
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
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];
}
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