Javascript - How to get multiple data attribute of button when on click - javascript

I have multiple buttons with a data-rel attribute, how do I get that attribute on each button when they are clicked?
HTML
<button class="category" data-rel="all">All</button>
<button class="category" data-rel="banana">Banana</button>
<button class="category" data-rel="orange">Orange</button>
<button class="category" data-rel="watermelon">Watermelon</button>
JS
var btn = document.querySelector('.category');
btn.addEventListener('click', function() {
var el = btn.getAttribute('data-rel');
alert(el);
})
I'm only able to get the attribute for the first button, but not the rest
var btn = document.querySelector('.category');
btn.addEventListener('click', function() {
var el = btn.getAttribute('data-rel');
alert(el);
})
.button {
padding: 10px 20px;
}
<button class="category" data-rel="all">All</button>
<button class="category" data-rel="banana">Banana</button>
<button class="category" data-rel="orange">Orange</button>
<button class="category" data-rel="watermelon">Watermelon</button>

I assume that you want a single function to handle every button. Therefore I declare the function outside of the loop, so it is declared only one time, and therefore saves memory. You are going to need this handle-function regardless of what version you use below to add the event listener.
// The handle function is declared outside the loop, saving memory.
// The current button is found in this
// Also using dataset to get the data.
function handle(event) {
alert( this.dataset.rel );
}
To get all matching elements you need to use querySelectorAll. It returns a collection, and you can use the forEach-method to easily traverse the collection:
document.querySelectorAll('.category').forEach(
function (btn) {
btn.addEventListener('click', handle );
}
);
This is the same type of call, but with modern arrow-function instead. I don't know what you prefer.
document.querySelectorAll('.category').forEach(
btn => btn.addEventListener('click', handle )
);

Related

Variable value won't increment by 1 in Javascript

var countWrong = 0;
(Button1, Button3, Button4, Button5, Button6).addEventListener('click', () => {
countWrong += 1;
});
console.log(countWrong)
I can not figure out what I'm doing wrong. When the buttons are clicked I want to increment 1 to countWrong.
There are two problems here:
(Button1, Button3, Button4, Button5, Button6).addEventListener
You can't call a method on multiple objects like that. I assume you want something like:
[Button1, Button3, Button4, Button5, Button6].forEach(b =>
b.addEventListener('click', () => {
countWrong += 1;
})
);
EDIT: I assumed Button... where variables, but if they are IDs, you'll need to look them up first, maybe like this:
document.querySelectorAll("#Button1, #Button3, #Button4, #Button5, #Button6").forEach( ... )
Also console.log(countWrong) will always display 0, because the event handlers won't have been called yet.
You can do what you need with jquery
var countWrong = 0;
$('#Button1, #Button3, #Button4, #Button5, #Button6').on('click', () => {
countWrong += 1;
console.log(countWrong)
});
And the console.log() has to be inside your onclick function otherwise your log will only give you the initial value 0 once before any button was pressed
You have written the right code however you see the wrong results.
Because, you are printing the countWrong only once after initialising it.
So when you initialise for the first time, it's value will be zero and it prints it.
And when ou click the buttons, the value of that variable will be updated however you won't be able to see because the console.log present in outer code has already been executed ( but the value is updating ).
And your logic of binding the events for multiple ids is not as same as you wrote, change a bit.
try this :
var countWrong = 0;
['Button1', 'Button3', 'Button4', 'Button5', 'Button6'].forEach(function(e) {
e.addEventListener('click', () => {
countWrong += 1;
console.log(countWrong)
});
});
As mentioned in my comment you can't assign an event listener to multiple elements like that. The other answers have covered iterating over the buttons with forEach - here's an example with event delegation.
Add one listener to a parent container which catches events as they "bubble" up the DOM from its children (the buttons). Within the handler check that the clicked element is a button (and has a "count" class), and then increase/log the count.
const buttons = document.querySelector('.buttons');
buttons.addEventListener('click', handleClick);
let count = 0;
function handleClick(e) {
if (e.target.matches('button')) {
if (e.target.classList.contains('count')) {
console.log(++count);
}
}
}
button { border-radius: 5px; }
.count { background-color: lightgreen; }
.count:hover { cursor: pointer; }
Green buttons increase the count
<section class="buttons">
<button type="button" class="count">Button 1</button>
<button type="button">Button 2</button>
<button type="button" class="count">Button 3</button>
<button type="button">Button 4</button>
</section>

How to pass arguments to the event listener's function so that there would be no duplicate event listeners?

Currently, I use the following solution:
<button onclick="initiate('ok2')" id="btn1">Initiate</button>
<button id="btn2">Send data</button>
function initiate(ok) {
document.getElementById("btn2").addEventListener("click", receiveData);
}
function receiveData(event) {
console.log(event);
}
The benefit of this approach lies in the named function receiveData, which is recognized as the same function and is not added repeatedly.
Steps to reproduce:
Press the 'Initiate' button multiple times
Press 'Send data'
Result: console log is printed only once
I want to utilize the same approach, but add an attribute to the function. I tried the bind approach, but the event listener is added multiple times. As a result, the console log is also printed multiple times.
Example:
function initiate(ok) {
document.getElementById("btn2").addEventListener("click", receiveData.bind(null, ok));
}
function receiveData(event, ok) {
console.log(event);
console.log(ok);
}
Is it possible to pass an argument to a function and not create duplicate event listeners? Ideally, it would be preferred not to delete event listeners, like in the current solution.
Here is my version with the recommended ways of delegating and setting and getting data attribute
A user cannot click what is not visible so no need to initiate the button, just unhide it
document.addEventListener("click", function(e) {
let btn = e.target
if (btn.matches("#btn1")) {
let targetBTN = document.getElementById(btn.dataset.target);
targetBTN.hidden = false;
} else if (btn.matches("#btn2")) {
console.log(btn.dataset.field);
}
});
<button id="btn1" data-target="btn2">Initiate</button>
<button id="btn2" data-field="ok2" hidden>Send data</button>
// when the window loads add a click handler to the button of choice
window.addEventListener('load', (event) => {
console.log('page is now loaded');
document.getElementById("btn2").addEventListener("click", receiveData)
});
function receiveData(event) {
console.log(event);
}
or as suggested in comments, add the click handler inline.
You need to tel it if it is inited or not..
let data = "";
let init = true;
function initiate(ok) {
data = ok
if(init ){
document.getElementById("btn2")
.addEventListener("click", receiveData);
init = false
}
}
function receiveData(event) {
console.log( data );
}
<button onclick="initiate('ok2')" id="btn1">Initiate</button>
<button id="btn2">Send data</button>
It looks like the one goal is to only allow the second button to be able to be used when the first button is clicked.
So, I attached an event listener to the document. Then used data attributes on the buttons to determine if the start button can be used or not. And just for display I used CSS to hide the start button if its not allowed to be used just yet
document.addEventListener("click",function(e){
let btn = e.target
if(btn.matches(".btn-start")){
let targetBTN = document.querySelector(`[data-id='${btn.dataset.target}']`)
targetBTN.setAttribute("data-initiated","true");
}
else if(btn.dataset.initiated == "true"){
console.log(btn.dataset.field);
}
});
[data-initiated="false"]{
display:none
}
[data-initiated="true"]{
display:inline-block
}
<button data-target="send2" class="btn-start">Initiate</button>
<button data-initiated="false" data-field="ok2" data-id="send2" class="btn-send">Send data</button>

JavaScript button value limited to first button only. How to overcome?

I'm struggling to get my JS to display the relevant output based on button selection.
I only get the first value returned, so selecting button A returns "You selected: A" as expected, but selecting button B or C does nothing.
Here's an example of my html:
let userSelection = document.querySelector('.userOutput');
let selected = document.querySelector('button');
selected.addEventListener('click', userPicked);
function userPicked() {
let choice = selected.value;
if (choice === 'a') {
userSelection.textContent = 'You selected: A';
} else if (choice === 'b') {
userSelection.textContent = 'You selected: B';
} else if (choice === 'c') {
userSelection.textContent = 'You selected: C';
} else {
userSelection.textContent = "Error"
}
}
<div class="allbuttons">
<button class="btn" value="a">A</button>
<button class="btn" value="b">B</button>
<button class="btn" value="c">C</button>
</div>
<p class="userOutput"></p>
I've seen that this could be affected by 'closure', but can't see how to overcome that specifically (if it is that).
You are using .querySelector() which finds the first element that matches your selector, in your case, the "A" button and that's the button that you've set up your click event handler on, so that's the only button that works. Instead, use event delegation and just return the value of the clicked event target.
let userSelection = document.querySelector('.userOutput');
// Set up a click event on the document, so any click will trigger the callback
document.addEventListener('click', userPicked);
function userPicked(evt) {
// Check to see if the actual object clicked is one of the buttons
if(evt.target.classList.contains("btn")){
// Return the value of the clicked button
userSelection.textContent = evt.target.value;
}
}
<div class="allbuttons">
<button class="btn" value="a">A</button>
<button class="btn" value="b">B</button>
<button class="btn" value="c">C</button>
</div>
<p class="userOutput"></p>
this a classic event delegation system:
const All_Buttons = document.querySelector('.allbuttons')
, user_Output = document.querySelector('.userOutput')
All_Buttons.onclick=e=>
{
if (e.target.className!='btn') return // ignore other clicks on area
user_Output.textContent = `You selected: ${e.target.value.toUpperCase()}`
}
<div class="allbuttons">
<button class="btn" value="a">A</button>
<button class="btn" value="b">B</button>
<button class="btn" value="c">C</button>
</div>
<p class="userOutput"></p>
You are attaching the click event to only the first button, Why ? As of the querySelector method returns the first occurrence of the selector parameter you pass to it (in your case the selector is button).
So instead, you could use the querySelectorAll method to get all the buttons (based on the selector) and then you HAVE to cycle through the returned array like object (NodeList containing an Element object for each element matching the selector).
So, here's the updated code :
let userSelection = document.querySelector('.userOutput'),
selected = document.querySelectorAll('button.btn'); // just to be more specific.
// loop through the buttons.
[].forEach.call(selected, (el) => {
// add the listener
el.addEventListener('click', () => userSelection.textContent = el.value);
});
<div class="allbuttons">
<button class="btn" value="a">A</button>
<button class="btn" value="b">B</button>
<button class="btn" value="c">C</button>
</div>
<p class="userOutput"></p>
Am here for any further clarifications.
let userSelection = document.querySelector('.userOutput');
let selected = document.querySelectorAll('.btn');
selected.forEach((x,i) => {
x.addEventListener('click', function() {
userSelection.innerHTML = 'You have selected: ' + this.textContent
})
})
Scotts answer works as well, this is another way to handle it.
querySelector() only targets the first element of its kind. If you use querySelectorAll() you get a variable that holds an object of all instances of the element. That allows you to iterate over it. I also recommend using id="someId" for your output element, for more precision.
jQuery Solution
let userSelection = $('.userOutput')
let selected = $('.btn')
selected.click(function() {
userSelection.html('You have selected: ' + $(this).text())
})
https://codepen.io/mujakovic/pen/zVJRKG
From querySelector
The Document method querySelector() returns the first Element within the document that matches the specified selector, or group of selectors. If no matches are found, null is returned.
While you should really implement event delegation (as demonstrated in a number of places), to get a collection of elements what you probably want is querySelectorAll which you could use to modify your selection to get all your buttons and add the event handler to all of them:
Array.prototype.slice.call(document.querySelectorAll('.btn'))
.forEach((selected) => selected.addEventListener('click', userPicked) )
You should also use this in the event handler instead of attempting to use the global selected since the value is not being updated during the click event.
Be careful here though, there is a trade-off between adding an event handler to every single object and adding an event handler to the entire page. Adding handlers to all objects does not scale well. Adding a global handler means you may need to be aware of anything else that might need to interact with the same event. #MisterJojo's answer is a good example of event delegation that restricts the handler to just the containing element.
Demo:
[...document.querySelectorAll('.btn')]
.forEach((selected) => selected.addEventListener('click', userPicked));
let outputForButton = (value) => {
switch(value) {
case 'a': return 'You selected: A';
case 'b': return 'You selected: B';
case 'c': return 'You selected: C';
default: return 'Error';
}
}
function userPicked() {
document.querySelector('.userOutput').textContent =
outputForButton(this.value);
}
<div class="allbuttons">
<button class="btn" value="a">A</button>
<button class="btn" value="b">B</button>
<button class="btn" value="c">C</button>
</div>
<p class="userOutput"></p>

How to access outside .this in object

I'm trying to rewrite my functional code to module pattern js, and I have this issue - When I try to delete input field which is dynamically created, I use jQuery $(this) to access dom element and delete its parent 'div'. But this refers to the Modal object, not the component I clicked. How to solve it, without making some field counter and creating fields with unique ID's, then catching ids on click and deleting that input field?
My modal:
var s,
Modal = {
settings: {
addInputBtn: $("#add-input"),
inputContainer: $("#modal-input-form"),
checkBoxesList: $(".check-box"),
inputFieldsList: $(".form-control"),
inputFieldsOptionalList: $(".optional-modal"),
inputHtml: `
<div class="input-group mb-3 optional-modal">
<div class="input-group-prepend">
<div class="input-group-text">
<input type="checkbox" class="check-box">
</div>
</div>
<input type="text" class="form-control">
<button type="button" class="close">
<span>×</span>
</button>
</div>`
},
init: function () {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function () {
s.addInputBtn.on("click", () => Modal.addInput());
s.inputContainer.on("click", ".close", () => Modal.deleteInput());
},
addInput: function () {
s.inputContainer.append(s.inputHtml);
},
deleteInput: function () {);
$(this).parent('div').remove();
}
}
Modal.init();
deleteInput: function (e) {);
$(e.target).parent('div').remove();
}
Event handlers are passed an event argument. Among its useful properties is target, which is the html element that the event originated on. Note that it could be different from the this of the event handler, which is the element that the event handler is attached to, as events bubble up through the DOM. So if you have this html:
<div id="parent">
<div id="child"></div>
</div>
then for this JS function:
$("#parent").on('click', function(e){
//this will equal <div id="parent"> unless you bind the handler to something else
//e.target will equal the clicked element:
//if the user clicked the child, it will equal the child;
//if the user clicked parent directly, it will equal parent.
$(e.target).closest('#parent').doSomething();
});
Did you try with:
deleteInput: function () {;
$(this.settings).parent('div').remove();
}
Also you have a typo at deleteInput: function () { ); ... the rest of the code, delete that extra ) after function. I suggest you trying $(this.settings).. because, this gets the Modal, and then you need to access the other object inside that object, which is settings. So your modal object, consists of other object, and I'm thinking this way you should be able to get it :)

toggle display in pure javascript

I am trying to toggle visibility of signup and signin boxes if sign in and sign up buttons are clicked. I am trying to use only pure javascript.
I wrote simple html and javascript as below:
<div>
<button class="signin">sign in</button><button class="signup">sign up</button>
<div class="signin-box" style="display: none;">
<form class="signin-form">
<label>username<input></label><label>password<input></label><button type="submit">signin</button>
</form>
</div>
<div class="signup-box" style="display: none;">
<form class="signup-form">
<label>username<input></label><label>password<input></label><button type="submit">signup</button>
</form>
</div>
</div>
javascript part:
var signupButton = document.getElementsByClassName('signup')[0];
var signinButton = document.getElementsByClassName('signin')[0];
var signupBox = document.getElementsByClassName('signup-box')[0];
var signipBox = document.getElementsByClassName('signin-box')[0];
console.log("box: ", signupBox, "button: ",signupButton);
var toggleVisible = function(item){
if (item.style.display === 'none'){
return item.style.display = 'block';
}else{
return item.style.display = 'none';
}
};
window.onload = function(){
signupButton.onclick = toggleVisible(signupBox);
signinButton.onclick = toggleVisible(signipBox);
};
The problem here is that the javascript toggleVisible is automatically activated even if i never clicked the buttons.
as a result, the signin-box and signup-box both gets display:block property.
How do i solve this problem?
You're calling the function, not passing it in. Just wrap your function call in an anonymous function:
signupButton.onclick = function() {
toggleVisible(signupBox);
};
If you don't care about older browsers, you can also simplify your code a little if you put your JavaScript at the bottom of the <body> tag and add a rule to your CSS:
document.querySelector('.signup').addEventListener('click', function() {
document.querySelector('.signup-box').classList.toggle('hidden');
}, false);
document.querySelector('.signin').addEventListener('click', function() {
document.querySelector('.signin-box').classList.toggle('hidden');
}, false);
And the CSS:
.hidden {
display: none;
}
I would recommend to use a standard JavaScript method addEventListener() to attached onclick event listener to the button.
It has following advantages over different solution:
You can attach an event handler to an element without overwriting existing event handlers.
You can add many event handlers of the same type to one element, i.e
two "click" events.
In your code it will look like
window.onload = function(){
signupButton.addEventListener("click", function() { toggleVisible(signupBox); });
signinButton.addEventListener("click", function() { toggleVisible(signipBox); });
};
Current code invokes toggleVisible(..) method and assigns its result to the button attribute, which is not one would expect.
signupButton.onclick = toggleVisible(signupBox);

Categories