Can't understand how live HTMLCollection works - javascript

I have <ul class="container" id="container"></ul> where I conditionally add li elements. I create selector with - cards: Array.from(document.getElementsByTagName('li')). So when a program starts selector is empty, because there are no li elements. The problem then is using the cards selector in other parts of my code. If I import it from my selectors file it just won't work. But if I create same selector in a function that needs cards, it works. As I understand getElementsByTagName should make updates automatically.
The getElementsByTagName method of Document interface returns an HTMLCollection of elements with the given tag name. The complete document is searched, including the root node. The returned HTMLCollection is live, meaning that it updates itself automatically to stay in sync with the DOM tree without having to call document.getElementsByTagName() again.
Question why imported selector won't work when the same selector declared in function that uses it works? It seems to me that I'm missing something.
Code example: base.js
export const elements = {
cards: Array.from(document.getElementsByTagName('li')),
...
}
deckview.js
import { elements } from './base'
export const toggleClassOnClick = () => {
// this works
let cards = Array.from(document.getElementsByTagName('li'))
// this won't work
// let cards = elements.cards
cards.forEach(card => {
card.addEventListener('click', function() {
card.classList.value === 'card'
? card.classList.add('is-flipped')
: card.classList.remove('is-flipped')
// disabling ability to press on the opened card
if (card.classList.value === 'card is-flipped') {
card.style.pointerEvents = 'none'
}
})
})
}

document.getElementsByTagName('li') will return a live collection, but Array.from will take the data from it and make an regular array. The array is just an array, it isn't live.
const lis = document.getElementsByTagName('li');
const array = Array.from(lis);
console.log("Before: ", lis.length, array.length);
const li = document.createElement("li");
li.textContent = "Goodbye";
document.querySelector("ul").appendChild(li);
console.log("After: ", lis.length, array.length);
<ul><li>Hello</li></ul>

Related

Why it says forEach is not a function? [duplicate]

I want to use lazy loading effect and in my code everything is going ok, but querySelector section.
I've stored some elements in a variable and I wanna apply observer function on theme.
And I also print every output in console.log to see the things.
But this error happens:
Uncaught TypeError: imgs.forEach is not a function
This is my code:
const imgs = document.querySelector("[data-src]");
const options = {
threshold:1,
};
const observer = new IntersectionObserver((entries,observer) => {
entries.forEach(entry => {
let src = entry.target.getAttribute('data-src');
if(!entry.isIntersecting){
return;
}
entry.target.src=src;
console.log(entry)
observer.unobserve(entry.target);
})
},options)
// Probleme is here
imgs.forEach(img => {
observer.observe(img);
})
Try with querySelectorAll like below code,
const imgs = document.querySelectorAll("[data-src]");
as querySelector will return single matched element, but querySelectorAll will return an NodeList of all matched elements.
Use querySelectorAll instead that should fix it
Reference the mdn documentation
The Document method querySelectorAll() returns a static (not live)
NodeList representing a list of the document's elements that match the
specified group of selectors.
instead of
const imgs = document.querySelector("[data-src]");
try this
const imgs = document.querySelectorAll("[data-src]");
Although for some browser using querySelectorAll(selector).forEach() will work. But it is not supported by all browsers. So the safe way to do this is using a for loop for looping over the NodeList. So you should do something like this,
const imgs = document.querySelector("[data-src]");
// other coddes...
for(let i = 0; i<imgs.length; i++) {
// your code.
}
I think this will be the safest way.
Document.querySelector()
returns a single element and you cannot loop over a single element.
document.querySelectorAll()
returns all the elements with the class, id, tagname etc provided in the parenthesis.

Can't use forEach on querySelector

I want to use lazy loading effect and in my code everything is going ok, but querySelector section.
I've stored some elements in a variable and I wanna apply observer function on theme.
And I also print every output in console.log to see the things.
But this error happens:
Uncaught TypeError: imgs.forEach is not a function
This is my code:
const imgs = document.querySelector("[data-src]");
const options = {
threshold:1,
};
const observer = new IntersectionObserver((entries,observer) => {
entries.forEach(entry => {
let src = entry.target.getAttribute('data-src');
if(!entry.isIntersecting){
return;
}
entry.target.src=src;
console.log(entry)
observer.unobserve(entry.target);
})
},options)
// Probleme is here
imgs.forEach(img => {
observer.observe(img);
})
Try with querySelectorAll like below code,
const imgs = document.querySelectorAll("[data-src]");
as querySelector will return single matched element, but querySelectorAll will return an NodeList of all matched elements.
Use querySelectorAll instead that should fix it
Reference the mdn documentation
The Document method querySelectorAll() returns a static (not live)
NodeList representing a list of the document's elements that match the
specified group of selectors.
instead of
const imgs = document.querySelector("[data-src]");
try this
const imgs = document.querySelectorAll("[data-src]");
Although for some browser using querySelectorAll(selector).forEach() will work. But it is not supported by all browsers. So the safe way to do this is using a for loop for looping over the NodeList. So you should do something like this,
const imgs = document.querySelector("[data-src]");
// other coddes...
for(let i = 0; i<imgs.length; i++) {
// your code.
}
I think this will be the safest way.
Document.querySelector()
returns a single element and you cannot loop over a single element.
document.querySelectorAll()
returns all the elements with the class, id, tagname etc provided in the parenthesis.

How to run script two times

I have two the same forms on the same page and script that works only for the first form.
I'm a beginner and this is a challenge for me; I tried add the `for (var i = 0; i < input.length; i++) but it doesn't work out. I will be grateful for any help.
var el = document.querySelector(".js-tac");
input = document.querySelector('.js-tel')
input.addEventListener('input', evt => {
const value = input.value
if (!value) {
el.classList.remove("is-visible");
return
}
const trimmed = value.trim()
if (trimmed) {
el.classList.add("is-visible");
} else {
el.classList.remove("is-visible");
}
})
document.querySelector return the first matched element. So you need document.querySelectorAll which will give a collection. Then iterate that collection like this
document.querySelectorAll('.js-tel').forEach((input)=>{
// not using arrow function since using this to target the element
input.addEventListener('input', function(evt){
const value = this.value
// rest of the code
})
})
The problem is that you are only getting one input element. (querySelector returns the first matching element, not all matching elements). You likely want to use querySelectorAll to get a NodeList (which will contain all matching nodes). You can iterate over those.
Based on how you seem to be using it, I'd recommend making sure your js-tac and js-tel are wrapped in some common parent, and use querySelectorAll to find those. Then, you can use querySelector to find the js-tel and js-tac.
var nodes = document.querySelectorAll('.js-parent')
//If you don't export forEach to be available, you can also just do a standard
//for loop here instead.
nodes.forEach((parent) => {
var el = parent.querySelector(".js-tac");
input = parent.querySelector('.js-tel')
...
})

How to call/edit all html elements with class names that are found inside an array?

I have managed to create the array with the names that I need. These names are pushed or removed from the array based on user’s clicks on various html elements(buttons).
I am attempting to use the values collected within the array to call changes upon html elements that have class names corresponding/matching the names within the array.
I have managed to create a function that activates a window alert that allows me to see and verify that I am able to cycle through all elements collected within the array. But I got stuck. I couldn’t figure out how to use the individual values/names within the array to call the specific classes of html elements.
I have tried:
for (var a = 0; a < array.length; a++) {
document.getElelemntsByClassName(“.”+array[a]).classList.add(“new”);
//and//
document.querySelectorAll(“.”+array[a]).classList.add(“new”);
//none of them worked. So I wasn’t able to get through to the specific html elements.//
window.alert(“.”+array[a]);
//This responds properly. I can get multiple alerts, one at the time, with all the names I am expecting to see.//
}
Thank you in advance for your help.
I believe you want to use an object instead of an array, since indexes on an array will change as you remove items. That said, you may not even need the object, depending on what you want to do with the element. In the snippet below, I added classNames as an object to treat it as an associative array, for example:
// This is shared between two functions
const LIST_ITEM_SELECTOR = '.js-list-item'
// Get top-level elements
const listElement = document.querySelector('.js-list')
const listItemTemplate = document.querySelector('.js-list-item-template')
const addButton = document.querySelector('.js-add-button')
const logButton = document.querySelector('.js-log-button')
// Replaces "array" from your example
const classNames = {}
// Removes the list item from the list element (also delete from classNames)
const handleDelete = e => {
const parentListItem = e.currentTarget.closest(LIST_ITEM_SELECTOR)
const listItemId = parentListItem.dataset.id
delete classNames[listItemId]
parentListItem.remove()
}
// Updates after each "Add"
let nextId = 0
const handleAdd = () => {
// Build new element from the template
const newListItem = listItemTemplate.content
.cloneNode(true)
.querySelector(LIST_ITEM_SELECTOR)
// Add class to the element and the "classNames" object
const className = `id-${nextId}`
newListItem.classList.add(className)
classNames[nextId] = className
// Add data-id
newListItem.dataset.id = nextId
// Update text
newListItem.querySelector('.js-text').textContent = `Item Text ${nextId}`
// Add delete event listener to the nested x button
newListItem.querySelector('.js-x-button').addEventListener('click', handleDelete)
// Append the newListItem to the end of the list
listElement.appendChild(newListItem)
// Prep the nextId for the next "Add" click
nextId += 1
}
addButton.addEventListener('click', handleAdd)
logButton.addEventListener('click', () => {
console.dir(classNames)
})
<button class="js-add-button">Add</button>
<ul class="js-list"></ul>
<template class="js-list-item-template">
<li class="js-list-item">
<span class="js-text">Item Text</span>
<button class="js-x-button">x</button>
</li>
</template>
<button class="js-log-button">Log Out Data</button>

Get element by PART of class name [JavaScript]

I'm trying to make a chrome extension. A part of the code (early on in the making of this extension) involves fetching several elements by their class name. There are several elements whos class name all look like this: "scollerItem [AlotOfRandomCharacters]".
So I'm trying to list all classNames that start with "scrollerItem", but i'm not quite sure how to go about it.
So here's my code so far:
function initRE(){
var matchingItems = [];
var allElements = document.getElementsByTagName("div");
for(i=0; i < allElements.length; i++)
{
if ( allElements [i].className == "scrollerItem" && "*" )
{
matchingItems.push(allElements[i]);
}
}
alert(matchingItems[0]);
}
allElements is listed in the breakpoint watchlist as "HTMLCollection(623)" (roughly), but nothing is forwarded to the "matchingItems" array.
I've tried [i].className.contains and .includes
Direct copy of one of the HTML Elements in question:
<div class="scrollerItem s1d8yj03-2 ebdCEL Post t3_agnhuk s1ukwo15-0 RqhAo" id="t3_agnhuk" tabindex="-1">
You can try to use Document.querySelectorAll() with a CSS selector matching all classes starting with your target string.
let elems = document.querySelectorAll("div[class^='scrollerItem'], div[class*=' scrollerItem]");
let classes = Array.from(elems).map(e => Array.from(e.classList)).reduce((arr, res) => {
res = res.concat(arr);
return res;
}, []).filter(cls => cls.startsWith('scrollerItem'))
Additional explanation of CSS selector syntax: Is there a CSS selector by class prefix?
Since the class scrollerItem exists on the element, you can use document.querySelectorAll() with the .scrollerItem as the query:
const result = document.querySelectorAll('.scrollerItem');
console.log(Array.from(result));
<div class="scrollerItem s1d8yj03-2 ebdCEL Post t3_agnhuk s1ukwo15-0 RqhAo" id="t3_agnhuk" tabindex="-1">
Use classList not className:
if (allElements[i].classList.contains("scrollerItem") {...}

Categories