Dynamically created content has a delete option. I capture the event via
function requestDeleteItem() {
$(".list").on("click", ".fa-trash-alt", event => {
const itemId = $(event.currentTarget)
.parent()
.parent()
.attr("id");
const itemName = $(event.currentTarget)
.parent()
.siblings("span.name")
.text();
confirmDeleteItem(itemName, itemId);
});
}
In the confirmDeleteItem function a confirmation button is generated and the AJAX call is made via JQuery.
The delete option for each item has an icon. I can click on the icon for multiple items and then click the confirm button on the final one and it will delete all previously clicked items, rather than just one.
I want to delete just the last clicked item. I'm thinking it has to do with the current event target and that it gets saved in memory, but i am not advanced enough to more.
You need to move your $(".list").on event outside of requestDeleteItem.
What is happening is that every time you call requestDeleteItem() (to create a ajax button), you are also creating a handler for every list. Thus, your event handling code gets called over and over again when the button is clicked. Instead, when using event delegation, you only need to add the handler once, as it will pick up any buttons added later.
Here's a simplified code snippet that shows the correct behavior. Note how
$("#container").on('click' is only called once.
let num = 0;
const requestDeleteItem = () => {
$("#container").append(`<button class="del">delete ${num}</button>`);
num++;
}
$("#add").on('click', () => {
requestDeleteItem();
});
$("#container").on('click', '.del', (e) => {
console.log(`clicked on ${e.currentTarget.innerHTML}`);
})
.del {
display: block;
}
#container {
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id=add>add button</button>
<div id="container"></div>
For comparison, here's a BAD example that adds the event handler every time:
let num = 0;
const requestDeleteItem = () => {
$("#container").append(`<button class="del">delete ${num}</button>`);
num++;
$("#container").on('click', '.del', (e) => {
console.log(`clicked on ${e.currentTarget.innerHTML}`);
})
}
$("#add").on('click', () => {
requestDeleteItem();
});
.del {
display: block;
}
#container {
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id=add>add button</button>
<div id="container"></div>
Related
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>
I have an array of NodeList. I loop through every NodeList element and add click event Listener.
var itemsList = document.querySelectorAll('.items');
itemList.forEach(item => {
item.addEventListener('click', () => {
console.log('clicked');
})
})
As soon as I clicked any one of the items I want to remove the event listener for all the other items as well.
It doesn't matter each item is clicked or not.
To do it directly without jQuery or anything, and without overcomplicating it, you could do something like this:
const itemsList = document.querySelectorAll('.items');
const onClick = () => {
console.log('clicked');
itemsList.forEach(item => {
item.removeEventListener('click', onClick);
});
};
itemsList.forEach(item => {
item.addEventListener('click', onClick);
});
Basically you keep a reference to the click function, and the function itself removes itself from all nodes in the list.
If you want to know which item was clicked, you can add a parameter to the onClick function, which will be the click event, from which you can get the item that was clicked, like so:
const itemsList = document.querySelectorAll('.items');
const onClick = event => {
const clickedItem = event.target
console.log('clicked on ' + clickedItem.textContent);
itemsList.forEach(item => {
item.removeEventListener('click', onClick);
});
};
itemsList.forEach(item => {
item.addEventListener('click', onClick);
});
Something along these lines will let you get a reference to which item was actually clicked.
This is very concise using jquery
var handler=function(){
//your logic of click event
alert('clicked')
}
// handle click and add class
$('.items').on("click", function(){
handler();
$('.items').not(this).off("click");
})
This will removes every other click events after clicking the first one. If you want to remove every event including the first clicked, then remove the not() method from my code
One approach is to add the listener to an ancestor of your elements i.e. document + a target check on which element was clicked, if you need to remove this listener you only have to remove it once
function handleClick(event) {
if (event.target.classList.contains("items")) {
alert(event.target.textContent)
document.removeEventListener('click', handleClick)
}
}
document.addEventListener('click', handleClick);
<div id="app">
<li class="items">1</li>
<li class="items">2</li>
<li class="items">3</li>
<li class="items">4</li>
<li class="items">5</li>
</div>
You can make this more expressive
var itemsList = document.querySelectorAll('.items');
const updateClickListener = (list, callback, operation = 'add') => {
itemList.forEach(item => {
item[`${operation}EventListener`]('click', () => {
callback();
updateClickListener(list, () => {}, 'remove');
})
})
}
updateClickListener(
list,
() => {
console..log('clicked');
});
Here's my function:
document.querySelector('body').addEventListener('click',e=>{getRandomImagePair()});
When I click anywhere on the body something happens. I have two divs .more and .wd and if I click on them the function getRandomImagePair() executes. How can I make so that if I click the links the function doesn't fire?
I tried this below, it works but then the .more div won't trigger another needed different function.
$('.more, .wd').on('click', function(event) {
event.stopPropagation();
});
Using event delegation, just check the className of the event.target element. Use an if statement in your event handler callback to prevent getRandomImagePair from being called a link was clicked on:
function getRandomImagePair() {
console.log('getRandomImagePair called');
}
//using event delegation
document.querySelector('body').addEventListener('click', e => {
if (e.target.className !== 'wd' && e.target.className !== 'more') {
getRandomImagePair();
}
});
.wd,
.more {
color: blue;
text-decoration: underline;
}
<body>
<div class="more">.more</div>
<div class="wd">.wd</div>
<p>Here is some other stuff</p>
<p>that when clicked on should still fire event handler</p>
</body>
Try excluding the items you don't want to be affected by the click :
const getRandomImagePair = () => {
alert("clicked")
}
$('body :not(".more, .wd")').on('click', getRandomImagePair);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>Yep</button>
<button class="more">Nope</button>
<button class="wd">Nope</button>
<button>Yep</button>
When I click a button, I change its ID and apply a new style to it by adding a class toMenu. What I wanted to do is, when I click the button with the new ID menu, that it adds another class menuTransition. But what now happens is that it already adds the class menuTransition when I click the button with the old ID #button. But what it's supposed to do, is not add the class menuTransition until the button with the new ID #menu is clicked.
Here's my code:
$(document).ready(function() {
$("#button").click(function(){
$("#button").addClass("toMenu")
$("#button").attr("id","menu");
});
});
$(document).on("click", "#menu", function() {
$("#menu").addClass("menuTransition");
});
What you're seeing is a bit of a race condition. With your button click event handler you're adding a class and an ID to your button. Then with your delegated event handler you're looking for any clicks on the document, even those that bubble up from descendant elements, and adding a class there as well. One way to handle this is to add a small (~ 1 msec) delay to short-circuit this race that would normally occur with your example code.
$(document).ready(function() {
$("#button").click(function() {
$("#button").addClass("toMenu")
setTimeout(function() {
$("#button").attr("id", "menu");
}, 1)
});
});
$(document).on("click", "#menu", function() {
$("#menu").addClass("menuTransition");
});
.toMenu {
font-weight: bold;
}
.menuTransition {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button">
button
</button>
By adding the 1 millisecond delay, the ID is added after the click on the button has reached the document event handler, so that event handler only fires after the first click on the button.
You should not be changing the ID. Element IDs are intended to be static. You can use a class to tag the current state of the button / menu and make it behave accordingly (and at the same time avoid the inefficient delegated event handler on $(document):
$(document).ready(function() {
$("#menubutton").on('click', function() {
var $this = $(this);
if ($this.hasClass('toMenu')) {
$this.addClass('menuTransition');
} else {
$this.addClass('toMenu');
}
});
});
I'm trying to write a web app which replaces the context menu (right-click menu) with my own customized ones. I want it so that when the user clicks on a table row, they get one certain context menu and when they click on the background of the page, they get a different one.
I have already written the menus and gotten them working. The problem comes in when trying to figure out how to get the background's menu to show ONLY when clicking on the background and how to get the table row's menu to show when that is clicked.
I tried using document.body.oncontextmenu for the body and and setting the oncontextmenu function for each table row, but the body's oncontextmenu function overrides the row's so I get the wrong menu. The menu for the table rows DOES work if I stop using the body's menu, so that's not the issue.
I could be using the wrong events, so is there a different event for just the background (and not the elements on top of the background)? Or a way to "prioritize" the events so the table row's function takes precedence?
This is how the code looks:
var tableMenu;
var bodyMenu;
window.onload = function()
{
bodyMenu = new rightClickMenu("bodyMenu");
document.body.oncontextmenu = function() { bodyMenu.show(); tableMenu.hide(); }
bodyMenu.add("Add Entry", function()
{
alert("ADD");
});
tableMenu = new rightClickMenu("tableMenu", "tblSims");
simRows = getElementsByClassName("trSimRow");
for (var i in simRows)
simRows[i].oncontextmenu = function() { tableMenu.show(this.id.substring(2)); bodyMenu.hide(); }
tableMenu.add("Delete Entry", function(mac)
{
alert("DELETE");
});
document.body.onclick = function()
{
bodyMenu.hide();
tableMenu.hide();
};
}
You can capture the target element, e.g.:
$('*').click(function(e) {
alert(e.target);
alert(e.target.tagName);
if(e.target.tagName == 'html') {
// show background menu
}
});
You have to work with the Javascript Event Propagation model. What happens is that your click event is automatically passed down the layers of objects on a page that have been registered as event listeners, unless you explicitly tell it to stop, try something like this:
function setupClickHandlers()
{
document.getElementsByTagName('body')[0].onclick = doBodyMenu;
document.getElementById('tableID').onclick = doTableMenu;
}
function doBodyMenu()
{
//do whatever it does
}
function doTableMenu(e)
{
//do whatever it does
//stop the event propagating to the body element
var evt = e ? e : window.event;
if (evt.stopPropagation) {evt.stopPropagation();}
else {evt.cancelBubble=true;}
return false;
}
This should deal with the way each browser handles events.
$( document ).ready(function() {
var childClicked = false;
// myContainer is the nearest container div to the clickable elements
$("#myContainer").children().click(function(e) {
console.log('in element');
childClicked = true;
});
$("#myContainer").click(function(e){
if(!childClicked) {
console.log('in background');
e.preventDefault();
e.stopPropagation();
}
childClicked = false;
});
});
#myContainer {
width:200px;
height:200px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="myContainer" style="">
link
<div style="width:50px;height:50px;background-color: white;">
another link
</div>
</div>