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

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>

Related

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>

How to trigger a button tap with javascript?

I want to automatically trigger a tap on a button with javascript.
var submitButton = document.getElementsByName('name');
I tried the following and none of them worked.
submitButton.click();
and
const touchEvent = new TouchEvent("touchstart", {
touches: [touch],
view: window,
cancelable: true,
bubbles: true,
});
submitButton.dispatchEvent(touchEvent);
Neither worked.
Document.getElementsByName() returns a NodeList of elements (not a single element).
As far as the event, click should work just fine:
for (const elm of document.getElementsByTagName('button')) {
elm.addEventListener('click', (ev) => console.log(ev.target.textContent));
}
const buttons = document.getElementsByName('name');
for (const button of buttons) {
button.click();
}
<div>
<button name="name">one</button>
<button name="name">two</button>
<button>three</button>
</div>
Add an id to the above button with a onClick function.
Change the
document.getElementsByName('name')
to
document.getElementById('id')
Full Code:
Html:
<button id="btn" onClick="clicked()">
Click me
</button>
JS:
var btn=document.getElementById("btn");
btn.click();
function clicked() {
console.log("Clicked");
}
If you want to trigger multiple buttons with same class:
Html:
<button class="btn" onClick="clicked()">
Click me
</button>
JS:
var btn=document.getElementsByClassName("btn");
btn[0].click();
function clicked() {
console.log("Clicked");
}
For multiple button with same class, the return of the document.getElementsByClassName will return an array of object. In the above example, I have used the first element of that array, but if you want, you can loop through the array and trigger the click event.
document.getElementsByName returns a NodeList Collection of elements with a given name attribute in the document.
So your submitButton will be an array. So you have to dispatch the click event with submitButton[0].click()
var submitButton = document.getElementsByName('name');
submitButton[0].click();
function submitClick() {
console.log('submitClick')
}
<button name="name" onclick="submitClick()">Submit</button>

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

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 )
);

Get element from which onclick function is called

I have a button with a onclick attribute which is pointing to the function test().
<button onclick="test()">Button 1</button>
<button onclick="test()">Button 2</button>
<button onclick="test()">Button 3</button>
Function test():
function test()
{
var button_name = this.html;
console.log("Im button "+ button_name);
}
How can I get informations about the clicked button?
e.g. How can i read the html?
jsfiddle: https://jsfiddle.net/c2sc9j9e/
Pass the this reference to the function, then read textContent property the text content of the node.
HTML
<button onclick="test(this)">Button 1</button>
Script
function test(clickedElement){
var button_name = clickedElement.textContent;
}
Fiddle
Four options:
Pass this into the function.
<button onclick="test(this)">Button 1</button>
and then use that argument in the function.
Hook up the handlers with addEventListener or jQuery's on, and then use this within the handler.
var buttons = document.querySelectorAll("selector-for-the-buttons");
Array.prototype.forEach.call(buttons, function(btn) {
btn.addEventListener("click", handler, false);
});
function handler() {
// Use `this` here
}
jQuery version:
$("selector-for-the-buttons").on("click", function() {
// Use `this` here
});
Hook up a single handler on a container these buttons are in, and use the target property of the event object to determine which was clicked (but note that if you use other elements within button, you'll need to loop up to the button first).
document.querySelector("selector-for-the-container").addEventListener("click", function(e) {
// Use `e.target` here
}, false);
jQuery version that handles the possibility of nested elements within the button for you:
$("selector-for-the-container").on("click", "button", function() {
// Use `this` here (note this is different from the DOM version above)
});
I came across an other extremely simple way to do it in Vanilla JS so I post it here for reference:
function whoami () {
var caller = event.target;
alert("I'm " + caller.textContent);
}
<button onclick="whoami()">Button 1</button>
<button onclick="whoami()">Button 2</button>
<button onclick="whoami()">Button 3</button>
I'm not sure about the browser support for it but it works at least on Safari, Firefox and Blink based browsers.
function test(button)
{
var button_name = button.getAttribute('name');
console.log("Im button "+ button_name);
}
<button onclick="test(this)" name="button1">Button 1</button>
<button onclick="test(this)" name="button2">Button 2</button>
<button onclick="test(this)" name="button3">Button 3</button>
If you want to use Jquery, then you can call the $(this) object in the function.
you must pass "this" to function
<button onclick="test(this)">1</button>
<button onclick="test(this)">2</button>
<button onclick="test(this)">3</button>
<script>
function test(t)
{
console.log(t);
}
</script>
Here is your solution jsfiddle , using jquery.
<button onclick="test(this)">1</button>
<button onclick="test(this)">2</button>
<button onclick="test(this)">3</button>
<script>
function test(button)
{
var button_name = $(button).html();
alert("Im button "+ button_name);
}
</script>
just add id to each button and pass it to your test function
and here is working jsfiddle
<button onclick="test(this.id)" id="button1">1</button>
<button onclick="test(this.id)" id="button2">2</button>
<button onclick="test(this.id)" id="button3">3</button>
<script>
function test(id)
{
var button_name = id;
alert("Im button name is : "+ button_name);
console.log("Im button name is :"+ button_name);
}
</script>
What you want is the event that triggers the click, and you do that by specifying the function call as MyFunction(event). For example:
<ul>
<li onclick="MyFunction(event)">Red</li>
<li onclick="MyFunction(event)">Orange</li>
<li onclick="MyFunction(event)">Yellow</li>
</ul>
and then your Javascript function can be:
function MyFunction(ev) {
// Now you have access to everything in the event
//- including the triggering element
var element = ev.srcElement;
}
By leaving out the (event) parameter in the specification of the onclick function call you don't get it.

how to judge which button has been clicked using there className instead of ID

I have 100 buttons in a table having same class name but different id c-1,c-2,....,c-n <input type="button" class="btn-c" id="c-1" value="ADD">
how will i Know which button has been clicked using their className and whithout using onclick event on the each button
<input type="button" ... onclick="call_function(this);"
for simplicity let say I want to alert(button.id); on the click of any of the 100 buttons
If you have so many buttons, it makes sense to use event delegation:
$('table').on('click', '.btn-c', function() {
alert(this.id); // will get you clicked button id
});
This is optimal approach for performance standpoint as you bind only one event handler to parent element and benefit from child element event bubbling.
UPD. This is pure javascript version of the same code:
document.getElementById('table').addEventListener('click', function(e) {
if (/\bbtn-c\b/.test(e.target.className)) {
alert(e.target.id);
}
}, false);
Demo: http://jsfiddle.net/zn0os4n8/
Using jQuery - attach a click handler to the common class and use the instance of this to get the id of the clicked button
$(".btn-c").click(function() {
alert(this.id); //id of the clicked button
});
You need to attach an event to a parent element and listen for the clicks. You can than use the event object to determine what is being clicked on. You can check if it is the element you want and do whatever you want.
document.body.addEventListener("click", function (e) { //attach to element that is a parent of the buttons
var clickedElem = e.target; //find the element that is clicked on
var isC = (clickedElem.classList.contains("c")); //see if it has the class you are looking for
var outStr = isC ? "Yes" : "No"; //just outputting something to the screen
document.getElementById("out").textContent = outStr + " : " + clickedElem.id;
});
<button class="d" id="b0">x</button>
<button class="c" id="b1">y</button>
<button class="c" id="b2">y</button>
<button class="c" id="b3">y</button>
<button class="d" id="b4">x</button>
<div id="out"></div>
Note: this is not going to work in older IEs without polyfills.

Categories