JavaScript click event listener on class - javascript

I'm currently trying to write some JavaScript to get the attribute of the class that has been clicked. I know that to do this the correct way, I should use an event listener.
My code is as follows:
var classname = document.getElementsByClassName("classname");
var myFunction = function() {
var attribute = this.getAttribute("data-myattribute");
alert(attribute);
};
classname.addEventListener('click', myFunction(), false);
I was expecting to get an alert box every time I clicked on one of the classes to tell me the attribute but unfortunately this does not work. Can anyone help please?
(Note - I can quite easily do this in jQuery but I would NOT like to use it)

This should work. getElementsByClassName returns an Array-like object (see below) of the elements matching the criteria.
var elements = document.getElementsByClassName("classname");
var myFunction = function() {
var attribute = this.getAttribute("data-myattribute");
alert(attribute);
};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', myFunction, false);
}
jQuery does the looping part for you, which you need to do in plain JavaScript.
If you have ES6 support you can replace your last line with:
Array.from(elements).forEach(function(element) {
element.addEventListener('click', myFunction);
});
Note: Older browsers (like IE6, IE7, IE8) donĀ“t support getElementsByClassName and so they return undefined.
Details on getElementsByClassName
getElementsByClassName doesn't return an array, but a HTMLCollection in most, or a NodeList in some browsers (Mozilla ref). Both of these types are Array-Like, (meaning that they have a length property and the objects can be accessed via their index), but are not strictly an Array or inherited from an Array (meaning other methods that can be performed on an Array cannot be performed on these types).
Thanks to user #Nemo for pointing this out and having me dig in to fully understand.

With modern JavaScript it can be done like this:
const divs = document.querySelectorAll('.a');
divs.forEach(el => el.addEventListener('click', event => {
console.log(event.target.getAttribute("data-el"));
}));
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Example</title>
<style>
.a {
background-color:red;
height: 33px;
display: flex;
align-items: center;
margin-bottom: 10px;
cursor: pointer;
}
.b {
background-color:#00AA00;
height: 50px;
display: flex;
align-items: center;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="a" data-el="1">1</div>
<div class="b" data-el="no-click-handler">2</div>
<div class="a" data-el="3">11</div>
</body>
</html>
Gets all elements by class name
Loops over all elements with using forEach
Attach an event listener on each element
Uses event.target to retrieve more information for specific element

* This was edited to allow for children of the target class to trigger the events. See bottom of the answer for details. *
An alternative answer to add an event listener to a class where items are frequently being added and removed. This is inspired by jQuery's on function where you can pass in a selector for a child element that the event is listening on.
var base = document.querySelector('#base'); // the container for the variable content
var selector = '.card'; // any css selector for children
base.addEventListener('click', function(event) {
// find the closest parent of the event target that
// matches the selector
var closest = event.target.closest(selector);
if (closest && base.contains(closest)) {
// handle class event
}
});
Fiddle: https://jsfiddle.net/u6oje7af/94/
This will listen for clicks on children of the base element and if the target of a click has a parent matching the selector, the class event will be handled. You can add and remove elements as you like without having to add more click listeners to the individual elements. This will catch them all even for elements added after this listener was added, just like the jQuery functionality (which I imagine is somewhat similar under the hood).
This depends on the events propagating, so if you stopPropagation on the event somewhere else, this may not work. Also, the closest function has some compatibility issues with IE apparently (what doesn't?).
This could be made into a function if you need to do this type of action listening repeatedly, like
function addChildEventListener(base, eventName, selector, handler) {
base.addEventListener(eventName, function(event) {
var closest = event.target.closest(selector);
if (closest && base.contains(closest)) {
// passes the event to the handler and sets `this`
// in the handler as the closest parent matching the
// selector from the target element of the event
handler.call(closest, event);
}
});
}
=========================================
EDIT: This post originally used the matches function for DOM elements on the event target, but this restricted the targets of events to the direct class only. It has been updated to use the closest function instead, allowing for events on children of the desired class to trigger the events as well. The original matches code can be found at the original fiddle:
https://jsfiddle.net/u6oje7af/23/

You can use the code below:
document.body.addEventListener('click', function (evt) {
if (evt.target.className === 'databox') {
alert(this)
}
}, false);

Yow can use querySelectorAll to select all the classes and loop through them to assign the eventListener. The if condition checks if it contains the class name.
const arrClass = document.querySelectorAll(".className");
for (let i of arrClass) {
i.addEventListener("click", (e) => {
if (e.target.classList.contains("className")) {
console.log("Perfrom Action")
}
})
}

Also consider that if you click a button, the target of the event listener is not necessaily the button itself, but whatever content inside the button you clicked on. You can reference the element to which you assigned the listener using the currentTarget property. Here is a pretty solution in modern ES using a single statement:
document.querySelectorAll(".myClassName").forEach(i => i.addEventListener(
"click",
e => {
alert(e.currentTarget.dataset.myDataContent);
}));

All the above-mentioned answers are correct and I just want to demonstrate another short way
document.querySelectorAll('.classname').forEach( button => {
button.onclick = function () {
// rest of code
}
});

Here's a different approach for many DOM elements with the same class name by selecting path key in the eventListener object.
Add an event listener to the immediate parent class wrapping all the child elements with the same class and get the path by selecting the first key in the event object.
E.g say you want to edit table cells of a table
//Select tbody element & add event listener
let tbody = document.querySelector('tbody');
tbody.addEventListener("click", function(e,v) {
// Get the clicked cell
let cell = e.path[0];
// Get the current cell value
let cellValue = cell.innerHTML;
//Rest of code goes here
}

Related

Is there a way to call eventListner on any of multiple elements with same className or ID. Only the first child got called in my code

devs.
I got stuck while building a project.
I have a list of menu items, about ten of them, with the same ID name and I will like to edit each of them if any
is clicked.
using js, I did this;
const menuElement = document.querySelector('#menuElement')
const darktheme = document.querySelector('.dark')
loadEventListener()
function loadEventListener() {
menuElement.addEventListener('click', draw)
}
function draw() {
menuElement.style.background = 'var(--primary-color)'
}
However, when I click on the first menu element, it responds. but, it does not for the rest.
Thanks.
Yes. But, instead of setting up multiple event listeners on all the elements you want, use "Event Delegation" and set up a single event listener on a common ancestor element, then let the event bubble up to that ancestor. In the event callback, you then check to see if the event originated at an element that you care to handle and, if so, handle it.
And note, that ids must be unique, however multiple elements can have the same class, so that's the way to go.
EXAMPLE:
// Set up an event handler on a common ancestor
document.addEventListener("click", function(event){
// Check to see if the event originated at an element with the "foo" class
if(event.target.classList.contains("foo")){
// Handle the event any way you want
event.target.classList.add("bold");
}
});
.bold { font-weight:bold; }
<p>Clilck on any of the lines. Only the ones with the "foo" class will get bolded.</p>
<div class="foo">XYZ</div>
<div class="fooBar">XYZ</div>
<div class="foo">XYZ</div>
<div class="fooBar">XYZ</div>
<div class="foo">XYZ</div>
<div class="fooBar">XYZ</div>
You can use querySelectorAll() to use a CSS selector and get all elements in the document that match that selector in a NodeList. Then you could use forEach() to attach an event listener to each of those elements.
Here a small working example.
window.addEventListener("DOMContentLoaded", e => {
document.querySelectorAll(".myclass").forEach((element, i) => {
element.addEventListener("click", () => console.log(`Div ${i} was clicked.`))
});
})
.myclass {
border: 1px solid black;
padding: 10px;
margin: 5px;
}
<div class="myclass">Div 0</div>
<div class="myclass">Div 1</div>
<div class="myclass">Div 2</div>
You should also wait for the DOM to be loaded before attaching event handlers using the DOMContentLoaded event.
You cannot use an ID as IDs must be unique!
Another way to do it is using event delegation as outlined in Scott Marcus's answer.

OffcanvasMenu plugin - click event, Shopware 6

I have a problem with adding a click event for list of OffcanvasMenu. I need add this event for every li element. I'm working with Shopware 6.
But when I'm trying to add click event for li element with jQuery, nothing happens.
I've extended OffcanvasMenu. Here is my code:
import OffcanvasMenuPlugin from 'src/plugin/main-menu/offcanvas-menu.plugin.js';
export default class ExtendOffcanvasMenuPlugin extends OffcanvasMenuPlugin {
init() {
super.init();
this._registerEvents();
console.log("inner");
}
_registerEvents() {
super._registerEvents();
let list = document.querySelector('.navigation-offcanvas-list-item');
console.log(list);
list.addEventListener('click', (e) => {
console.log("inner event");
e.preventDefault();
});
}
}
In main.js file I overriding this plugin:
PluginManager.override('OffcanvasMenu', ExtendOffcanvasMenuPlugin, '[data-offcanvas-menu]');
It's any possibility to add event on every li element in OffcanvasMenu plugin?
the general problem seems to be that the current code is using querySelector to collect all elements. However querySelector does only yield the first element which matches the selector, even if there are multiple ones, see: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
You can solve this by using querySelectorAll which does essentially the same but returns an iterable list of all found elements. Then you can iterate over the list and attach the listener to each element:
import OffcanvasMenuPlugin from 'src/plugin/main-menu/offcanvas-menu.plugin.js';
export default class ExtendOffcanvasMenuPlugin extends OffcanvasMenuPlugin {
_registerEvents() {
super._registerEvents();
let list = document.querySelectorAll('.navigation-offcanvas-list-item');
console.log(list);
list.forEach((listItem) => {
listItem.addEventListener('click', (e) => {
console.log("inner event");
e.preventDefault();
});
});
}
}
This would work in theory but in practice the a.navigation-offcanvas-link is inside the <li> where you want to attach the listener. The a.navigation-offcanvas-link element has already an event listener (_getLinkEventHandler) and also takes up all the space inside the <li> which is why the <li> itself is not actually clickable.
A workaround could be to make remove the listener first from a.navigation-offcanvas-link and make it non-clickable but it depends on what you want to achieve. Should something additional be executed everytime a menu item is being clicked? Then you could consider extending method _getLinkEventHandler directly even though it is marked as private and might change in future versions.

Two Html select drop down apply class for span element using Javascript

I am working on HTML select Dropdown. I have two dropdowns one is for font size adjust and other is for text alignment.
When I select the fontsize from the dropdown it has to apply along with text-capitalize (bootstrap css) and If I select the font alignment all three should apply for the span element. For Example.
<div>
<span id="Title"class="text-capitalize">check</span>
</div>
Right now the code was like this
function changeFont_size () {
var select = document.getElementById('font_size');
// Bind onchange event
select.onchange = function() {
document.querySelector("#Title").className = this.value += " text-
capitalize";
};
}
function changeAlignment () {
var select = document.getElementById('text_align');
// Bind onchange event
select.onchange = function() {
document.querySelector("#Title").className = this.value;
};
}
Actually I am newbe on Javascript. Some how I am not getting.
The output result would be the combination of :
<span class="h1 left text-capitalize">Text</span>
Everything should be in pure javascript.
Thanks in advance. Kindly help me.
Here is the Link
This jsfiddle makes your code work. You need to run the code when the document is loaded, so that your onchange functions are being hooked in time.
It does not work exactly like you intended though. Your alignment classes need to be on the parent element and when you select your alignment, you disregard the previously set h1 or h2 class.
window.onload = function() {
var font_size = document.querySelector('#font_size');
// Bind onchange event
font_size.onchange = function() {
document.querySelector("#Title").className = this.options[this.selectedIndex].value += " text-capitalize";
};
var text_align = document.querySelector('#text_align');
// Bind onchange event
text_align.onchange = function() {
document.querySelector("#Title").className = this.options[this.selectedIndex].value;
};
};
You are mixing things up. There are two ways to bind events (well, two ways which are still common even with recent browsers).
The first one is to put a function call in the onsomething property of an element in the html code. Whatever is put there will be executed when the event happens.
<button onclick="alert('hi');">Click me</button>
You should pass the event object to an event handler instead of writing inline code.
<button id="helloworld" onclick="helloworld_onclick(event)">Run</button>
...
function helloworld_onclick(e) {
alert("Hello world!");
}
If you want to be able to bind events dynamically, if you want to bind multiple events to an object and if you want to keep the JavaScript outside of your HTML, the modern way to to so is with addEventListener.
document.querySelector("#helloworld").addEventListener("click", function(e) {
alert("Hello world!");
});
The event object passed (called e in my functions) contains information about what triggered the event and can be used to prevent default behavior and to control event propagation. You can't use "this" in event handlers, but the element which called the handler will be stored in e.target.
In your code, you created functions which, when called, bind events to the elements. Then you bound those functions to the elements with the html attributes.
Finally, you seem to be stuck between querySelector and getElementById. Note that querySelector(All) returns a static node/nodelist while getElement(s)By(...) returns a live node/nodelist. A static node is a copy of all the information about the element. A live node is a reference to the real element. If you modify the element, it modifies the live node, but the static node will keep the old information. You should use getElementById over querySelector for that, and because it runs faster. For code simplicity however, you might prefer always using querySelector. Just don't mix using querySelector("#something") on a line and getElementById("something") on another one, it's the best way to get confused and end up wasting time on a bug because you wrote querySelector("something") or getElementById("#something") instead.
function changeFont_size (element) {
if(element.options[element.selectedIndex].value != 'select'){
document.getElementById('Title').className = element.options[element.selectedIndex].value;
} else{
document.getElementById('Title').className = '' }
}
function changeAlignment (element) {
if(element.options[element.selectedIndex].value != 'select'){
document.getElementById('container').className = element.options[element.selectedIndex].value;
} else{
document.getElementById('container').className = '' }
}
Try this, Hope it will work

.class selector not working

I'm working in a card game system that the player can select the card by clicking on it and then select the place to put it on. My problem is that when the player click on the target place, nothing happens.
This is my try: http://jsfiddle.net/5qMHz/
And this is my code:
function target() {
$(".target").on("click", function() {
$("#"+x).appendTo(this);
console.log(x);
});
};
What's wrong?
Try binding with document, since you change the class during document ready and there was no element with the class target initially
$(document).on("click",".target", function() {
$("#" + x).appendTo(this);
console.log(x);
}
WORKING FIDDLE
Firstly, your practice of putting function references in to jQuery objects is rather odd. The problem however is that because the .target class is applied after DOM load you need to use a delegate selector. Try this:
var $card
$(".card").on("click", function () {
$card = $(this);
if ($(".myslot").length) {
if ($(".myslot").is(':empty')) {
$(".myslot:empty").addClass("target");
} else {
alert('No empty slots');
}
}
});
$('.field').on('click', ".target", function () {
$card.appendTo(this);
$card = $();
});
Example fiddle
At the moment you are trying to bind the event handler, the elements don't have a class target yet. From the documentation:
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().
(Technically the elements exist, but they are not (yet) addressable by the class target)
You have three options to solve this:
Add the class to your HTML markup.
Bind the handler after you added the class to the elements.
Use event delegation.
The first two don't really fit to your use case, since your are adding the class target in response to an other event and the number of elements with the class target changes over time. This is a good use case for event delegation though:
$('.field').on('click', '.target', function() {
// ...
});

Writing this jQuery click function in JavaScript

I am slowly making my way from jQuery to vanilla JS, but I am struggling with wanting to write the following in JavaScript, would I have to setup a 'for' loop for each element? How would I target the child elements an an ID?
Here's what I would want to understand:
$('#id li').click(function(){
$(this).addClass('active');
});
I understand document getElementById to grab the '#id' but how would I achieve getting the child <li> elements? And how would I get the 'onclick' function to work for ANY list element clicked? Would I have to setup a 'for' loop for each element? Any help much appreciated!
Here is a JSFiddle that does what you want:
http://jsfiddle.net/digitalzebra/ZMXGC/10/
(function() {
var wrapper = document.getElementById("id");
var clickFunc = function(event) {
var target = event.originalTarget || event.target;
target.className = "active";
};
wrapper.addEventListener("click",clickFunc);
})();
A little bit of an explanation is in order...
First, I'm using a self executing function to fetch the wrapper div, using getElementById(). This is equivalent to using an id selector in jQuery: $('#id')
Next, I'm attaching a click handler to the element using the function addEventListener() and passing in an event type of click.
This binds the click handler function to the div element, the same as jQuery's click() would do. I'm using something called event bubbling, where a click event on any of the children of the wrapper will call the click handler attached to the wrapper.
Once the user clicks on the element, our function is called. originalTarget or target is the element that the user actually clicked in to, which in this case will always be an li. I'm then setting the class on that element to active.
Here is example for above question
http://jsfiddle.net/G3ADG/2/
(function() {
var id = document.getElementById("id");
var liele = id.getElementsByTagName("li");
for (var i = 0; i < liele.length; i++) {
liele[i].addEventListener("click", function () {
this.className = "active";
})
}
})();
Well I really liked Polaris878 solution as he is not using any loop inside it. In my solution first get HTML node information using document.getElementById this works similarly to $("#id"). than in next step I am fetching all tag type of "li" which are children of "id", than on this li tag array adding event listener to listen click functionality
className is one of attribute that allow to add class on that Node
I have tested above code in mozilla and chrome
This will work (IE >= 10), not want to search classList.add() replacement for IE<10,
var elems = document.querySelectorAll('#id li');
for (var i = 0; i < elems.length; i++) {
var elem=elems[i];
elem.addEventListener("click", function (e) {
this.classList.add('active');
});
}
fiddle

Categories

Resources