I am trying to make a functional recursion function for recursive DOM querying in JavaScript, here is my current attempt
//function to take a root DOM object and use a supplied query to return all the objects below that
function recurseTree(root, query, nodeList ) {
//run an initial query
let allNodes = root.querySelectorAll(query);
if (!allNodes.length) {
return [];
}
allNodes.forEach(function(node) {
return Array.from(allNodes).concat(recurseTree(node, query, nodeList));
});
}
I set up and call the function like so:
var ulList = [];
//calling recursive function to populate the list with all children that fit query
ulList = recurseTree(nav_root, "a + ul", ulList);
What is so bewildering to me is that whilst the array fed in (ulList) never deviates from an empty array when stepping through in debugger it actually sets ulList to undefined afterwards, as I try to print it to console afterwards
First the querySelectorAll function is already finding element in tree that match the selector. So your recursive function make no sense.
If you want to get a flat array of a NodeList you can do: (but you already know)
Old School
Array.prototype.slice.call(root.querySelectorAll(query))
Supported way
Array.from(root.querySelectorAll(query))
ES6
[...root.querySelectorAll(query)]
Related
I am trying to create a function that mimics Array.prototype.push.
It takes a variable number of arguments and pushes them into a specific array.
I have managed to do this with the following code:
var array=[];
function append(){
for(var i=0;i<arguments.length;i++)
array.push(arguments[i]);
}
Now my question is:Can I rewrite the append function without using "for loop"?
Thanks in advance.
If you need to get arguments array, you should use Array's slice function on an arguments object, and it will convert it into a standard JavaScript array:
var array = Array.prototype.slice.call(arguments);
You could use Array.prototype.push.apply
function append(){
// make arguments an array
var args = Array.prototype.slice.call(arguments);
// return the number of elements pushed in the array
return Array.prototype.push.apply(array, args);
}
So, what's happening here with args? We use Array.prototype.slice.call with arguments, the purpose being to make arguments an array, because it is a special object. Function.prototype.call is used to call a function with a specific context (aka this), and then the arguments to call the function with (comma separated). Conveniently, it appears that slice() looks at the length property of the this context, and arguments has one too, and when not empty, has properties from 0 to length -1, which allows slice to copy arguments in a new array.
You can rewrite this without a for loop, but you have to use a loop of some sort (you're working with multiple items, it's a necessity).
If you have access to ES6 or Babel, I would use something like:
function append(...args) {
return array.concat(args);
}
Without ES6, you need to work around the fact that arguments isn't a real array. You can still apply most of the array methods to it, by accessing them through the Array prototype. Converting arguments into an array is easy enough, then you can concat the two:
function append() {
var args = Array.prototype.map.call(arguments, function (it) {
return it;
});
return array.concat(args);
}
Bear in mind that neither of these will modify the global array, but will return a new array with the combined values that can be used on its own or assigned back to array. This is somewhat easier and more robust than trying to work with push, if you're willing to array = append(...).
Actually i honestly believe that push must be redefined for the functional JS since it's returning value is the length of the resulting array and it's most of the time useless. Such as when it's needed to push a value and pass an array as a parameter to a function you cant do it inline and things get messy. Instead i would like it to return a reference to the array it's called upon or even a new array from where i can get the length information anyway. My new push proposal would be as follows;
Array.prototype.push = function(...args) {
return args.reduce(function(p,c) {
p[p.length] = c;
return p
}, this)
};
It returns a perfect reference to the array it's called upon.
I have a some dom objects that are selected with :
var elems = document.getElementsByClassName("royal") ;
and also another objects
var collapsedElems = document.getElementsByClassName("collapsed");
my problem occured when i tried to concat elems and collapsedElems with array concat() method
elems.concat(collapsedElems)
but the return type of getElementsByClassName() is not array actually it is
object. I checked it at chrome console with typeof operator. that seems weird to me how can i combine this two group of object. ?
getElementsByClassName() returns an HTMLcollection object which is similar to an array but not really an array so you can't call array methods using the returned value.
One hack is to use Array's prorotype methods along with .call()/.apply() to pass the returned object as the context.
var elems = document.getElementsByClassName("royal") ;
var collapsedElems = document.getElementsByClassName("collapsed");
var earray = Array.prototype.slice.call(elems, 0);
var concatenated = earray.concat.apply(earray, collapsedElems) ;
console.log(concatenated)
Demo: Fiddle
It returns an HTML Collection which does things that arrays do not (such as getting live updates when the DOM changes).
If you want to get an array with elements from both collections in it, then you could:
Create an array and then populate it by looping over each of the collections and pushing each member into it (which will give you an array) or
Use document.querySelectorAll(".royal, .collapsed"); (gets a NodeList)
From the MDN:
Returns an array-like object of all child elements which have all of
the given class names. When called on the document object, the complete
document is searched, including the root node. You may also call
getElementsByClassName() on any element; it will return only elements
which are descendants of the specified root element with the given
class names.
You can try this:
function getElementsByClassName(className)
{
if (document.getElementsByClassName)
{
return document.getElementsByClassName(className);
}
else
{
return document.querySelectorAll('.' + className);
}
}
I have a function i call:
CreateNode(e,control);// which will return an ID.
// e i leave alone, but i was thinking that i
// could pass the object into the function this way optionally.
function CreateNode(e, control){
if(!control) control = this;
// for rest of function, calls to the object are $(control) instead of $(this).
//...
}
I then have a selector i want to iterate over:
$(control_group).each(createNode);
Is there a way to build a list of IDS from this, such as:
var arr = [];
arr.push($(control_group).each(createNode));
I am doing a recurive control builder which makes controls in controls, and so i want to return identifiers into a child attribute. That is what i was going to do with arr.
My one idea was doing something simple like:
var arr = [];
$(control_group).each(function(e){
arr.push(createNode(e,$(this));
});
That's exactly what .map() does:
var arr = $(control_group).map(createNode).get();
.map() returns a jQuery object; if you want an ordinary array, you need to .get() it.
Is it possible to call a function on specific index value of nodelist which is storing div like following :
var txtElem = txtdiv.getElementsByTagName("div");
the thing i want is that i am storing list of divisions in txtElem nodelist now i want to call a function on click event of the 3rd div stored in nodelist. The divisions are created dynamically and they don't have any id so they are not accessible by id.
from what you asked, it seems like this will do:
function toPseudoArray(nodeList) {
var ar = [];
for(var i in nodeList)
if(nodeList[i].nextSibling) // or for that case any other way to find if this is an element
ar.push(nodeList[i]);
return ar;
}
Pass your nodeList to this function, use what it returns as an array that contains your elements, and only your elements.
By the way, you could directly call function on a specific element simply using my_fab_function(txtElem[0]); -- of course, until you don't exceed the count.
The question is quite unclear. Seeing the jQuery tag, these come to my mind:
A way to call a jQuery function on a specified index using .eq():
var n = 1; //the index you need
$(txtElem).eq(n).css('color', 'red');
Simple Javascript to get the DOM element:
var n = 1; //the index you need
var elem = txtElem[n]; //elem will hold the DOM element
//call simple DOM methods on it:
var s = elem.innerHTML;
//you can also call jQuery functions on it:
$(elem).css('color', 'red');
By the way txtElem is not an object, it is a NodeList, an "array-like object".
I have a string that has data separated by a pipe character (|).
Example
var somestring = "data1|data2|data3";
var separated = somestring.split("|");
I know how to use the split() to separate each data.
However, I don't know how many pipes there will be in the resulting Array.
In jQuery or JavaScript, how do I loop over the array returned?
In jQuery or JavaScript, how do I loop through each separated variable?
You basically just need to iterate over the resulting Array.
jQuery
$.each loop
This method is easy to work with, and benefits in the variables used being encapsulated.
$.each(separated, function(index, chunk) {
// `chunk` is each member of the array.
});
jsFiddle.
Of course, jQuery is JavaScript so any of the below methods will also work.
JavaScript
for loop
This is the recommended way.
for (var i = 0, length = separated.length; i < length; i++) {
var chunk = separated[i];
// `chunk` is each member of the array.
}
jsFiddle.
You'll notice too the length property is cached so it is not looked up on each iteration. Some browsers already optimise for this, however IE appears to still benefit from it cached. It only takes 5 seconds to do, so you may as well keep IE users happy too.
You may want to define i and chunk outside of the for loop, because JavaScript has no block scope (unless you're using let), and those variables will exist before (declaration hoisted) and after (no block scope).
for ( in ) loop
This loop is generally not recommended, as it should be used for iterating over object properties only, not array like member properties.
for (var chunk in separated) {
if ( ! separated.hasOwnProperty(chunk)) {
continue;
}
// `separated[chunk]` is each member of the array.
}
jsFiddle.
This loop will loop over all properties up the prototype chain, so hasOwnProperty() must be used. For this reason it is not recommended for arrays.
for ( of ) loop
This loop is standardized in ECMA 6 and is able to loop over NodeLists and iterators.
for (var chunk of separated) {
// `chunk` is each member of the array.
}
jsFiddle
forEach() method
This method is an addition to the ECMA-262 standard. It's not available in IE8, but it can be shimmed relatively easily.
separated.forEach(function(chunk, index) {
// `chunk` is each member of the array.
});
jsFiddle.
Other specialised methods
If you're looking to iterate for a specific goal, it may be useful to use a specialised iterator. Keep in mind these also don't have the best browser support.
filter method
Creates a mew array of the elements which the associated callback returned truthy for.
separated.filter(function(element) {
return +element > 255;
});
reduce method
Creates a new value based on reducing the elements of the array, from left to right.
separated.reduce(function(accumulator, element) {
return accumulator.concat(element);
}, "");
See also the reduceRight method.
map method
Creates a new array, replacing each element with the returned value of the associated callback.
separated.map(function(element) {
return element.substr(0, 1);
});
every method
Returns a boolean value of which is the result of every element in the array passing the test. This method short circuits, i.e. it returns whenever one element's callback doesn't return truthy.
separated.every(function(element) {
return element.substr(0, 1) == "a";
});
some method
Returns a boolean value of which is the result of some element in the array passing the test. This method short circuits, i.e. it returns whenever one element's callback passes the test.
separated.some(function(element) {
return element.substr(0, 1) == "a";
});
separated.length should be all you need.
str.split() returns an array of values, so in your example, since 'separated' is an array, you could:
for (var i=0, len=separated.length; i < len; i++) {
// do something with separated[i]
}
you can do it in jquery like this
$.each(separated,function(key,item){ alert('here is ' + item + ' at position ' + key) })
If your question really is "how do I loop through each separated variable?" then:
for (var i = 0; i < separated.length; i++)
{
//Do something with separated[i];
}
//or (apparently this is deprecated)
for(var a in separated)
{
//Do something with a
}
Loop through with a FOR...NEXT construct like in most other languages:
var somestring = "data1|data2|data3";
var separated = somestring.split("|");
for (i=0 ; i<separated.length; i++) {
document.write(separated[i]);
document.write("<br/>");
}