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.
Related
I'm having difficulty with using Javascript to add a class to a button.
Context: I have multiple cards in an instragram type format with a post and a "like" button. I want to add a class of card__like-button which changes the image to a filled heart but i remembered that querySelector only applies to the first element, instead i tried using querySelectorAll but now the code doesn't apply to anything.
First attemp:
// Controls for active like button
let likeButton = document.querySelector(".card__like-button");
// Event listener for like button click
likeButton.addEventListener("click", likeButtonClick);
// Add class on button click
function likeButtonClick() {
likeButton.classList.toggle("card__like-button_active");
}
Second attemp
// Controls for active like button
let likeButton = document.querySelectorAll(".card__like-button");
// Event listener for like button click
likeButton.addEventListener("click", likeButtonClick);
// Add class on button click
function likeButtonClick() {
likeButton.classList.toggle("card__like-button_active");
}
I created the following codepen to replicate the problem.
I've search on the site, w3schools documentation, etc. and can't seem to find what am I doing wrong.
Because of the document.querySelectorAll method will return an array of NodeList details see here, you cannot directly assign the addEventListener event to a list.
Instead, you have to loop through each element and assign events to it
document.querySelectorAll(".card__like-button").forEach(ele => {
ele.addEventListener("click", likeButtonClick);
});
// Add class on button click
function likeButtonClick() {
this.classList.toggle("card__like-button_active");
}
Try it :
let likeButtons = document.querySelectorAll(".card__like-button");
Array.from(likeButtons).forEach(ele => {
ele.addEventListener("click", likeButtonClick);
});
// Add class on button click
function likeButtonClick() {
this.classList.toggle("card__like-button_active");
}
I am trying to add a CSS class to all objects that have been tab-key navigated to.
I have the following code, it's running a function on keydown checking if the tab key has been pressed. And tries to remove the tabbed CSS class if it already exists. But only one element on my page (top element) removes the CSS for all other elements. Instead of the class being removed if it exists when someone tab navigates there.
I use this code snipped in a Gatsby site
function checkTabPress(e) {
if (e.keyCode === 9) {
if(e.target.classList.contains('tabbed')) {
e.target.classList.remove('tabbed')
}else {
e.target.classList.add('tabbed');
}
}
}
document.addEventListener('keydown', checkTabPress);
Instead of adding event listener on the document, you should add one for each element you want to have this functionality (you should have ids for the elements). Otherwise the event listener will be called each time you tab, even if you do not tab the elements you want, will check the whole document as it is now doing it.
Maybe you can do something like this.
function checkTabPress(e) {
if (e.keyCode === 9) {
if(e.target.classList.contains('tabbed')) {
e.target.classList.remove('tabbed')
}else {
e.target.classList.add('tabbed');
}
}
}
var myElement = document.getElementById("my_cool_id");
myElement.addEventListener('keydown', checkTabPress);
//element
<div id="my_cool_id"/>
Another option, which i forgot about is this. You can loop through the elements including the class tabbed. However, this will work if all elements by default have this class.
document.querySelectorAll('.tabbed').forEach(item => {
item.addEventListener('keydown', checkTabPress)
})
Or you can just create a new class, for example called tabbable, which you will put on every element you want to have this event listener.
document.querySelectorAll('.tabbable').forEach(item => {
item.addEventListener('keydown', checkTabPress)
})
Okay, I have a todo app that i'm creating with JS. I have an addtodo function which adds the items when clicked and also runs a for loop for each of these list elements, which have a classname of 'list', to style them. The thing is, the for loop runs everytime I add the todo list which I think add's multiple event listeners to the already existing items with the same function. I'm trying to toggle a class for priority function when clicking on a paragraph with classname of 'thevalue'. My problem is the event listener runs multiple times and cancels the toggle. The first item runs once, which is correct, and the second item runs, twice, and the third runs three times as follow. I will attach the code below. It would be of great help if you could solve my issue.
var theListPara = document.getElementsByClassName('thevalue');
if (theListPara[0]) {
for (var i = 0; i < theListPara.length; i++) {
theListPara[i].addEventListener("click", changePriority);
}
function changePriority() {
var theClassName = this.parentElement.classList.contains('normal');
if (theClassName) {
this.parentElement.classList.toggle('high');
}
}
}
This whole line of code runs whenever the add todo is clicked.
Event Delegation is the way forward. Its philosophy is very simple, event listener is attached to static-parent-element then it analyses the bubbled event.target. if match is found then the desired operation can be performed.
document.querySelector('.static-parent-element').addEventListener("click", function(e) {
if(e.target && e.target.classList.contains('thevalue')) {
// thevalue item found
if (this.parentElement.classList.contains('normal')) {
this.parentElement.classList.toggle('high');
}
}
});
Element.matches() API can also used to validate element matches a given selector.
The Element.matches() method returns true if the element would be selected by the specified selector string; otherwise, returns false.
if(e.target.matches('.thevalue')){
}
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
}
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() {
// ...
});