I know I can do this with a simple for loop but I'm trying to understand better how to use forEach.
What I'm trying to do is, having a list of "a" elements coming from a querySelectorAll(), to obtain an array with the href attributes. What is wrong?
var links = document.querySelectorAll("a");
function get_hrefs(links){
var links_array = links.forEach(function(elem){ return elem.getAttribute("href"); });
return links_array;
}
get_hrefs(links);
Use slice to get an Array, like this:
var links = Array.prototype.slice.call(document.querySelectorAll("a"));
var links_array = links.map(function(elem){ return elem.getAttribute("href"); });
As for the updated question:
var links = document.querySelectorAll("a");
function get_hrefs(links){
links = Array.prototype.slice.call(links);
return links.map(function(elem){ return elem.getAttribute("href"); });;
}
get_hrefs(links);
You're looking for map rather than forEach, and you need to call it differently because querySelectorAll returns a NodeList, not an arary:
var links = document.querySelectorAll("a");
var links_array = Array.prototype.map.call(links, function(e){
return e.getAttribute("href");
});
That's if you really want the attribute value. If you want the resolved href, then:
var links = document.querySelectorAll("a");
var links_array = Array.prototype.map.call(links, function(e){
return e.href;
});
Re your updated question, where the function will receive links and not necessarily know what it is: The code above will work both for true arrays and for array-like things like the NodeList returned by querySelectorAll. So no changes needed to handle it.
If you want to use foreach, because querySelectorAll doesn't return array so you can do something like this:
var links = document.querySelectorAll("a");
var links_array = [];
[].forEach.call(
links,
function(elem){
links_array.push(elem.getAttribute("href"));
}
);
var hrefArray = [];
var links = document.querySelectorAll("a");
var Array.prototype.forEach.call(links, function(elem){ hrefArray.push(elem.getAttribute("href")); });
Let forEach push the elements into an empty array. forEach just iterates over all the elements. What you do with it is your decision.
Other users are pointing to the use of map. That functions suits the problem well. However the question was to get a better understanding of forEach.
Cerbrus pointed us to the fact that querySelectorAll returns a nodelist. Which I overlook at first. instead of an array. Now a nodelist is arrayish. So you can still treat it as one by using Array.prototype.forEach.call();
Since nodelist doesn't have the function forEach we need to invoke it from the Array object. Then we pass a reference to links using the first argument of call.
Related
I am adding table rows dynamically to a table using Javascript.
Is there a reason why items.length doesn't increase when I add a row?
I am also trying to sum the numbers contained in each row. But this doesn't work either. The dynamically added rows are completely ignored for some reason.
I am coming from jQuery where things like these used to work.
I am probably missing something really fundamental here. Thanks for any pointers.
document.addEventListener("DOMContentLoaded", function() {
var form = document.querySelector("#form");
var items = form.querySelectorAll(".item");
form.addEventListener("click", function(event) {
if (event.target.className == ".add_item") {
addFields(event);
}
});
function addFields(event) {
var item = document.createElement("template");
item.innerHTML = fields.trim();
items[items.length - 1].insertAdjacentElement("afterend", item.content.firstChild);
console.log(items.length);
event.preventDefault();
}
})
querySelector and querySelectorAll returns NodeList where as getElementsByClassName returns HTMLCollection.
The difference is, NodeList is a static copy but HTMLCollection is a live copy. So if element is modified, like in your case, a new row is added, HTMLCollection will work but NodeList will not.
So changing
var items = form.querySelectorAll(".item")
to
var items = form.getElementsByClassName("item")
might solve the problem.
Pointers
You cannot have a selector in getElementsByClassName. It expects a className, you cannot use composite selector like #form .item.
Reference:
Difference between HTMLCollection, NodeLists, and arrays of objects
You only query the items once here:
var items = form.querySelectorAll(".item");
form.addEventListener(
//you call addItem here
)
function addItem(){
//you access items.length here
}
You need to keep re-query-ing the selectors every time you add an item.
var items = form.querySelectorAll(".item"); //i got queried for the 1st time
function addFields(event) {
items = form.querySelectorAll(".item"); //get items again
//...
}
Or just don't query outside addFields() all in all. Put var items = ... in the function. There's no need to put it outside.
Read up #Rajesh's answer why this is so.
I have little to no experience of JavaScript but I do know that the getElementID only carries one value so how can I have 2 values passed?
Can I use it twice like I have down below or would I be better to use another GetElementBy/GetElementsBy method to do it?
<script type="text/javascript">
$(document).ready(function () {
hash();
function hash() {
var hashParams = window.location.hash.substr(1).split('&');
for (var i = 0; i < hashParams.length; i++) {
var p = hashParams[i].split('=');
document.getElementById("<%=start.ClientID%>").value = decodeURIComponent(p[1]);
document.getElementById("<%=end.ClientID%>").value = decodeURIComponent(p[1]);;
}
}
});
</script>
EDIT
So I've decided to use the loop twice and its working but the values I'm passing contain text I need removed. Is there a way in which I can cut off the split after a certain character? Here is my new code
<script type="text/javascript">
$(document).ready(function () {
hash();
function hash() {
var hashParams = window.location.hash.substr(1).split('#');
for (var i = 0; i < hashParams.length; i++) {
var p = hashParams[i].split('=');
document.getElementById("<%=start.ClientID%>").value = decodeURIComponent(p[1]);
}
var hashParams = window.location.hash.substr(1).split('&');
for (var i = 0; i < hashParams.length; i++) {
var p = hashParams[i].split('=');
document.getElementById("<%=end.ClientID%>").value = decodeURIComponent(p[1]);;
}
}
});
</script>
And here is the text that appears in the search bar when forwarded from the previous page.
localhost:56363/Bookings.aspx#start=27/02/2018 12:30&end=27/02/2018 17:30
The start and end input boxes fill with the values but the start input box (27/02/2018 12:30&end) has characters I want cut off (&end).
Is there a way to stop a split after a certain character?
Using it twice as you have is perfectly acceptable. And, if they are separate things, then it makes sense.
While you could also use getElementsByTagName(), getElementsByName() or getElementsByClassName(), usually using document.querySelectorAll() is the more modern choice.
If they have something in common with them (like say a class), you could use it like this:
const nodeList = document.querySelectorAll('.classToGet');
Array.prototype.forEach.call(nodeList, element => element.value = decodeURIComponent(p[1]));
document.querySelectorAll() (as well as the getElementsBy functions) return a NodeList, which is kind of like an Array, but doesn't have an Array's functions, so you need to Array.prototype.forEach.call() to loop over them.
document.querySelectorAll() accepts a string like you would give to CSS, and the NodeList has all elements that match that.
And FYI, there is an equivalent document.querySelector() which gets a single element, so you could use it for IDs:
document.querySelector("#<%=start.ClientID%>")
Note the # like you would have for CSS at the beginning.
ID is a unique identifier, unlike class, so there should be only one of it with the same name in your DOM.
getElementById is intended to find the one element in the DOM with the specified ID.
If you need to get multiple elements, then yes make multiple calls to getElementById.
See here for the documentation on the getElementById method showing that it only accepts a single ID parameter: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
Being fairly new to jquery and javascript I can't seem to understand how the .text() method really works. I read through the jQuery documentation but still can't figure it out.
for (var i=0; i < arrayLength; i++){
var currentElement = $(".artist")[i];
var currentArtist = currentElement.text;
console.log(currentElement);
console.log(currentArtist);
}
currentArtist returns "undefined" in the console. It works fine on the $(".artist") alone, but not when I use the [i] or anything additional for that matter. What am I missing here? How else could I grab a text value inside a selector?
By using the [] operator on jQuery object you're accessing the raw element node that was found by jQuery. This raw element doesn't have the jQuery methods anymore, nor a text property.
If you want to get single element from jQuery object and keep the jQuery wrapper, use eq method.
var artistElement = $(".artist").eq(i);
artistElement.text(); // gets the text content of the element
The code you've posted is also not very optimized. For instance, with every loop iteration you're searching the document over and over again for elements with class artist. Better to cache that search result in a variable before performing the loop. And if the loop iterates over all .artist elements, you can use jQuery's each method.
$(".artist").each(function () {
var artist = $(this); // this poits to the raw element thus wrapping into jQuery object
console.log(artist.text());
});
var currentArtist = currentElement.text;
Should be:
var currentArtist = currentElement.text();
You should use a each():
$(".artist").each(function(i,val){
var currentArtist = $(val).text();
console.log(val);
console.log(currentArtist);
});
$(".artist") produce a jQuery object that could be like this:
[div, div, div, div, prevObject: jQuery.fn.jQuery.init, context: document, selector: ".artist"...]
So the result of $(".artist")[i] is a HTMLElement and do not have a text method, that's why you're getting undefined
Also text() is a function and may be followed with ()
But if you want to keep the for loop you can do
for (var i=0; i < arrayLength; i++){
var currentElement = $(".artist")[i];
var currentArtist = $(currentElement).text();
console.log(currentElement);
console.log(currentArtist);
}
.text() shows the text of an html element or set of html elements that would be visible to the user.
I need to pass some html code as a parameter, however, before I pass it, I need to change some src attribute values.
I cannot use lastIndexOf or any of those to modify the html value since I don't know which value the src's will have.
What I'm trying to do then, is to create an object containing the html, and then alter that object only. I don't want to alter the actual webpage.
is this possible??
What I did first was this:
$('[myImages]').each(function() {
var urlImg = "../tmpFiles/fileName" + counter;
$(this).attr('src', urlImg);
counter++;
});
So finally, I had the desired code like this:
myformData = { theChartCode: $('#TheDivContainingTheHTML').html() }
However, this actually changes the image sources on the webpage, which I don't want to.
Then I thought I could create a JQuery object with the html so I could alter that object only like this:
var $jQueryObject = $($.parseHTML($('#TheDivContainingTheHTML').html()));
But now, I can't figure out how to iterate within that object in order to change the src attribute's values of the desired images.
Any help will be really appreciated ;)
There are several ways to do It. First would be creating a clone of target element and use the same on the Fly. You can do like below:
var Elem = $('#TheDivContainingTheHTML').clone();
now do whatever you want like iterate, alter,insert,remove.
var allImages =$(Elem).children("img");
Thanks Much!
Depending on when you want to change the object, solution will be different. Let's pretend you want to change it after you click another element in the page. Your code will look like that :
var clonedHTML;
$('#clickable-element').click(function() {
var $originalHTML = $(this).find('.html-block');
var $cloneHTML = $originalHTML.clone();
$cloneHTML.find('.my-image').attr('src', 'newSrcValue');
clonedHTML = $cloneHTML.clone();
return false; //Prevents click to be propagated
});
//Now you can use `clonedHTML`
The key point here is the clone method : http://api.jquery.com/clone/.
You can clone the elements:
var outerHTML = $collection.clone().attr('src', function(index) {
return "../tmpFiles/fileName" + index;
}).wrapAll('<div/>').parent().html();
You can also use the map method:
var arr = $collection.map(function(i) {
return $(this).clone().attr('src', '...').prop('outerHTML');
}).get();
how do i extract all the img html tags out of the javascript variable that looks like this:
var content = '<img src="http://website.com/image.jpg"><img src="http://website.com/image2.jpg"><img src="http://website.com/image3.jpg">Other content';
And then extract each img link , probably using jQuery .each() function.
Image link extracted will be
http://website.com/image.jpg
http://website.com/image2.jpg
http://website.com/image3.jpg
How can I do this? do i need regex?
You can create a jQuery collection of html elements from a valid html string without even appending them to any document. This will work like a normal jQuery collection.
$(content).filter('img').each(function () {
console.log($(this).attr('src'));
});
EDIT: If the images will always be the children of another element in content, use .find instead of .filter. Otherwise, you have to use both simultaneously, which you can still do:
$(content).find('img').addBack().filter('img').each(function () {
For single-level DOM this will work nicely:
var links = $(content).filter('img').map(function() {
return this.src;
}).toArray();
console.log(links); //["http://website.com/image.jpg", "http://website.com/image2.jpg", "http://website.com/image3.jpg"]
Fiddle
If you have multiple levels of DOM (nested images inside other elements) change first line to:
var links = $('<div>').append(content).find('img').map(function() {
Also, if you're scraping arbitrary data, you should use $.parseHTML (jQuery 1.8+) to be safe from XSS.
Here's the complete, sturdy version:
var links = $('<div>').append($.parseHTML(content)).find('img').map(function() {
return this.src;
}).toArray();
jsBin
In the above example, I'm appending the content to a dynamically created div element so I can perform just a single find operation. The result is the same as #Explosion Pills' .find('img').addBack().filter('img') variation.
Also, obviously, if you already have the images in the page, you can use a common ancestor as the root for .find() instead of creating new DOM elements:
var links = $('#imagesParent').find('img').map(function() {
You're right, using .each() to iterate over it works fine.
var ary = [];
var content = '<img src="http://website.com/image.jpg"><img src="http://website.com/image2.jpg"><img src="http://website.com/image3.jpg">Other content';
$(content).each(function () {
ary.push($(this).attr('src'));
});
jsFiddle example
LIVE DEMO Using .map() and .get()
var srcs = $(content).map(function(){
return this.src;
}).get();
http://api.jquery.com/map/
http://api.jquery.com/get/
LIVE DEMO Using .each() and an array to store data
var srcArray = [];
$(content).each(function(){
srcArray.push(this.src);
});
http://api.jquery.com/each/
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push