Javascript Query All Selectors and do something - javascript

Goal:
Apply action to each checked element if checked, vs just the one element.
I am setting up multiple buttons (custom components) that when clicked, the border changes for the tag within . When clicked again, the border resets.
I have 'checked' properly displaying when I click on the flash-card-check-mark (elm), however my code is only selecting the first item and ignoring the rest.
Can you point me in the right direction as to how I would go about making sure that -- any -- of the buttons being clicked will have this action applied?
(new to javascript and appreciate your insight)
Note: This is for code example (doesn't run without the component checked but shows the markup I'm using -- I would share a link but this is running locally)
document.querySelector('flash-card-check-mark').onclick = function() {
var elem = document.querySelector('flash-card-check-mark');
var elemContent = document.querySelector('flash-card-check-mark p');
if (elem.getAttribute("checked") === null) {
elemContent.style.border = "1px solid #0000";
} else {
elemContent.style.border = "1px solid magenta";
}
};
<flash-card-check-mark no-default-padding="true">
<p align="left" size="small" class="pa-2">
<span slot="heading">Start my marketing today</span>
</p>
</flash-card-check-mark>
<flash-card-check-mark no-default-padding="true">
<p align="left" size="small" class="pa-2">
<span slot="heading">Create automated customer journeys</span>
</p>
</flash-card-check-mark>

You have two options:
Apply the click handler to all elements with querySelectorAll
Use Event Delegation to apply a handler to the document, and then only pick clicks that were on "interesting elements".
Apply to all elements
Use querySelectorAll:
for(const elem of document.querySelectorAll('flash-card-check-mark')) {
// better to use `element.addEventListener('click', (e) =>
element.onclick = function(e) {
// the element is available in the listener
var elem = e.target;
var elemContent = elem.querySelector('p'); // you can querySelector an element
if (elem.getAttribute("checked") === null) {
elemContent.style.border = "1px solid #0000";
} else {
elemContent.style.border = "1px solid magenta";
}
};
Use Event Delegation
You add a listener to the document instead and then filter the element out using e.target:
document.addEventListener('click', e => { // can also onclick
// filter only elements you care about
if (!e.target.matches('flash-card-check-mark')) return;
var elem = e.target;
var elemContent = e.querySelector('p'); // query selector nests
if (elem.getAttribute("checked") === null) {
elemContent.style.border = "1px solid #0000";
} else {
elemContent.style.border = "1px solid magenta";
}
};
Notes
You can improve the code style itself with the following advice:
Use let/const instead of var statements as they have much less confusing scoping rules.
Use arrow functions (() => {}) since they are shorter and have a more obvious this value.
Use addEventListener('click', to add event listeners instead of setting handlers with onclick. It has less confusing scoping rules and it means you don't have conflicts with other people setting handlers on the element.
Use properties instead of attributes. So elem.checked and not elem.getAttribute('checked') === 'checked').

If you want to select multiple element, Use querySelectorAll.
Query Selector all Returns a nodeList.
On that node list you can use forEach Loop
const flashCard = document.querySelectorAll("flash-card-check-mark");
const elemContent = document.querySelector("p");
function myFunction() {
if (elemContent.getAttribute("checked") === null) {
elemContent.style.border = "1px solid #0000";
} else {
elemContent.style.border = "1px solid magenta";
}
}
flashCard.forEach(flashcard => flashcard.addEventListener("click", myFunction));

Maybe something like this (I've changed the colors):
[...document.querySelectorAll('flash-card-check-mark')].forEach(el => {
el.onclick = () => {
var elemContent = document.querySelector('flash-card-check-mark p');
elemContent.style.border = el.getAttribute('checked') === null ? '1px solid red' : '1px solid green';
};
});

With var elemContent = document.querySelector('flash-card-check-mark p');
you always select the first matching element in the page
The click is behaviour of your Custom Element
Then the Custom Element should add and handle the click
If you want to select elements inside a Custom Element,
use this.querySelect... not document.querySelect...
Since you use shadowDOM <slot> also read ::slotted CSS selector for nested children in shadowDOM slot for
<flash-card-check-mark>
<span slot="heading">Start my marketing today</span>
</flash-card-check-mark>
<flash-card-check-mark checked>
<span slot="heading">Create automated customer journeys</span>
</flash-card-check-mark>
<script>
customElements.define("flash-card-check-mark", class extends HTMLElement {
constructor(){
let style = `<style>:host{display:block;cursor:pointer}</style>`;
super()
.attachShadow({mode:"open"})
.innerHTML = style + `<slot name="heading"></slot>`;
this.onclick = (evt) => this.clicked();
this.clicked();
}
clicked(){
this.toggleAttribute("checked", !this.hasAttribute("checked") );
let color = "pink";
if (this.hasAttribute("checked")) color = "lightgreen";
this.style.backgroundColor = color;
}
});
</script>

Related

How to style/target a node that is created within a function, from outside of the function

I want to be able to style (or change) div(s) created within a function. However, this "change" must not be made in the very same function. Given that variables created in functions is in the local scope of that function, how do I reach the created div(s) for later styling (or other changes for that matter) from outside? I have tried targeting the node with querySelector but Console returns Uncaught TypeError since it cannot find the variable.
Example code in JS:
let container = document.querySelector('.container');
let divClass = document.querySelector('.div-class');
divClass.style = "backgroundColor: green";
container.addEventListener('click', () => {
let createdDiv = document.createElement('div');
container.appendChild(createdDiv);
createdDiv.classList.add('div-class');
})
The problem with your code is that you query before the element has even been created.
let container = document.querySelector('.container');
container.addEventListener('click', () => {
let createdDiv = document.createElement('div');
container.appendChild(createdDiv);
createdDiv.classList.add('div-class');
})
let someButton= document.querySelector('.someButton');
someButton.addEventListener('click', () => {
let divClass = document.querySelector('.div-class');
divClass.style.backgroundColor = "green";
})
.container,.someButton{
border:1px solid black;
background:grey;
margin-bottom:4em;
}
.div-class{
height:40px;
width:40px;
border:1px solid red;
}
<div class="container">
container
</div>
<div class="someButton">
someButton
</div>
In this snippet, the access to the created div is put in another click event. If you click .someButton before .container it will create an error, but if you click after it will work since the div will be created.
You can make a variable in global scope, and assign it the div which you create later.
const container = document.querySelector('.container');
// assign sample element to avoid error if new_div is used before assigning.
let newDiv = document.createElement('div');
container.addEventListener('click', () => {
let newDiv = document.createElement('div');
container.appendChild(newDiv);
newDiv.classList.add('div-class');
})
newDiv.style = "backgroundColor: green";

Checking a checkbox and applying the style to the corresponding input value

I'm creating a todo list in vanilla JavaScript. Right now, I'm trying to apply a style to a todo when the corresponding checkbox is checked.
toDoContainer.addEventListener("change", function(e) {
const tgt = e.target;
if (tgt.classList.contains("checkbox")) {
let toDo = tgt.closest("input").classList.contains("input");
if (toDo.value !== "") {
toDo.style.opacity = "50%";
}
}
});
As it stands, nothing is happening visibly and I'm not getting an error.
I'm using the closest() method because the checkbox and the todo are both inputs that are right next to eachother in the DOM.
Before I was using this code -
allCheckboxes.forEach(box => {
box.addEventListener("change", () => {
if (box.checked) {
for (let toDo = 0; toDo < allToDos.length; toDo++) {
if (allToDos[toDo].value) {
allToDos[toDo].style.textDecoration = "line-through";
allToDos[toDo].style.opacity = "50%";
}
}
}
else {
console.log("cat");
for (let toDo = 0; toDo < allToDos.length; toDo++) {
if (allToDos[toDo].value) {
allToDos[toDo].style.textDecoration = "none";
allToDos[toDo].style.opacity = "100%";
}
}
}
})
})
I was able to apply the styles to the inputs but only when the first checkbox in the node list was checked. Checking the checkbox also applied the styles to all of the todos in the DOM, not just the corresponding one which is what I want.
Another quirk that I've just noticed is that when I look at dev tools to see if it's logging the checkboxes it shows an empty node list - [] when there are multiple checkboxes visible.
document.getElementById("add").addEventListener("click", () => {
console.log(allCheckboxes);
});
How can I apply styling to only the corresponding todo when a checkbox is checked?
*** EDIT - https://codepen.io/harri-greeves/pen/LYLEKNj ***
I'm using the closest() method because the checkbox and the todo are both inputs that are right next to each other in the DOM.
The closest method searches the element itself and then it searches the element's ancestors, moving up the dom tree.
I don't think .closest can be used to target siblings.
https://api.jquery.com/closest/
The problem is on line:
const allCheckboxes = toDoContainer.querySelectorAll("[type=checkbox]");
querySelector returns not living html collection.
It means that it will not include dynamically added todos.
The better way is to listen parent node: todoContainer and then manipulate state.
add this function
function ChangeOpacity() {
let checkbox = document.getElementsByClassName("checkbox");
let input = document.getElementsByClassName("input");
for (let i = 0; i < checkbox.length; i++) {
checkbox[i].addEventListener("change",()=>{
if (checkbox[i].checked == true) {
if (input[i].value) {
input[i].style.opacity = "50%"
input[i].style.textDecoration = "line-through"
}
else{
input[i].style.opacity = "100%"
input[i].style.textDecoration = "none"
}
} else {
if (input[i].value) {
input[i].style.opacity = "50%"
input[i].style.textDecoration = "line-through"
}
else{
input[i].style.opacity = "100%"
input[i].style.textDecoration = "none"
}
}
})
}
}
ChangeOpacity()
and then call this function to the createToDo function
everything should work fine and its cleaner this way

how do you deselect a word when clicking on it again?

How to deselect a word after making it clickable
What I want to do is add (I'm assuming) another JS so when they click on the word again, it'll appear back to normal without the black background?
Rather than setting inline values for your code, just create a CSS class to handle this, then toggle as necessary.
element.onclick = () => {
element.classList.toggle('highlighted');
}
And the class ...
.highlighted {
background-color: black;
color: white;
}
A messier solution would be to add an if/else statement to your onclick function.
element.onclick = () => {
if (element.style.background == '#000') {
element.style.background = '#fff';
}
else {
element.style.background = '#000';
}
}
The .split() function actually removes your spaces, so just make sure you fix that when appending everything back together as well.
inputValue.split(" ").forEach(word => {
const element = document.createElement("span");
element.innerHTML = word + ' ';
...
I've mocked up the class solution at codepen.io.

Javascript, change colour of element on-click with if-statement

I have no idea how to change the colour of my element if the colour's crimson to white. It has me confused because I've tried many solutions such as turning them into variables. Would anyone be able to tell me what I'm doing wrong, or possibly point me in the right direction? I've tried "duplicate" questions, but none of them really share the same issue.
<button class="btn-startcall10" onclick="recorda()"><i class="fa fa-wave-square"></i> </button>
function recorda() {
document.getElementsByClassName("fa-wave-square")[0].style.color = "crimson";
if () {}
}
you can try something like this
function changeColor(){
el = document.getElementById("fa-wave-square");
if(el.style.color === 'crimson'){
el.style.color = 'white';
} else {
el.style.color = 'crimson';
}
}
https://jsfiddle.net/Lyuvf9a6/3/
You can use Element.style.color in the javascript to get the current color of the element.
Then based on that color you can change the color of your element.
let clickElement = document.getElementById("span-to-change-color");
clickElement.addEventListener("click", changeColor);
function changeColor() {
if (clickElement.style.color == "red") {
clickElement.style.color = "blue";
} else {
clickElement.style.color = "red";
}
}
<span style="color: red;" id="span-to-change-color">I am red(Click Me)</span>
First of all you have to add an event listener for on click event e.g
YOUR_ELEMENT.addEventListener("click", YOUR_LISTENER) // e.g recorda
Your event listener will get the event object from where you can access the object but if it's a nested object e.g your event listener is on div but you have a span inside and on click of span on click event will be triggered and the target object will be the span. But you can cache YOUR_ELEMENT and use that also.
Now you can check the style color for the color and do as necessary.
if (YOUR_ELEMENT.style.color === 'crimson') {
YOUR_ELEMENT.style.color = 'white'
} else {
YOUR_ELEMENT.style.color = 'crimson'
}
Here is a sample code e.g
<div id="textChange">Some text</div>
<script>
var elem = document.getElementsById('textChange')
function changeColor(e) {
// You can use and get elem using
// var ele = e.target
// Or as we have cached elem we can use that
if (elem.style.color === 'red') {
elem.style.color = 'green'
} else {
elem.style.color = 'red'
}
}
elem.addEventListener('click', changeColor)
</script>

Scope issues inside an Event Listener?

The following code basically shows/hides paragraph tags, I'm having to re-declare the paras variable. Is this because I'm dynamically injecting the button into the DOM, or is it to do with scope? How could I better construct this markup?
// vars
var revealContainer = document.querySelector('.reveal-more');
var paras = revealContainer.querySelectorAll('p');
var status = true;
// return
if (paras && paras.length <= 3) return;
// generate show more link
revealContainer.innerHTML += '<button class="button--text reveal-more__btn">Read more</button>';
var revealBtn = revealContainer.querySelector('.reveal-more__btn');
// click event
revealBtn.addEventListener('click', function () {
var paras = revealContainer.querySelectorAll('p');
// toggle show/hide class
for (var i = 0; i < paras.length; i++) {
var p = paras[i];
p.classList.toggle('is-shown');
}
// check status
if (status) {
this.textContent = 'Read less';
status = false;
} else {
this.textContent = 'Read more';
status = true;
}
});
You can use the live HTMLCollection returned by .getElementsByTagName() instead of the static NodeList returned by .querySelectorAll()
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.
var paragraphs = document.getElementById("container").getElementsByTagName("p");
console.log(paragraphs.length);
setInterval(function() {
document.getElementById("container").insertAdjacentHTML("beforeend", "<p>p</p>");
}, 1000);
setInterval(function() {
console.log(paragraphs.length);
}, 2000);
<div id="container"></div>
Below is a really simple Snippet that demonstrates delegated events in pure Javascript, instead of using jQuery.
Here you can see I've attached the eventListener to the div with id elements, this will then listen for click events under this, a simple matches is used just in case you have other elements your not interested in..
document.querySelector("#elements").addEventListener("click", (e) => {
if (!e.target.matches('.element')) return
console.log(`Clicked ${e.target.innerText}`);
});
.element {
border: 1px solid black;
margin: 5px;
}
<div id="elements">
<div class="element">1</div>
<div class="element">2</div>
<div class="element">3</div>
<div>Clicking this does nothing.</div>
</div>

Categories