I have an image with an overlay of a trash can in the top right corner. I have two click events, one when the user clicks the trash 'i.removeEvent' and the second when the user clicks the image 'div.spaceEvent', they both do different things when clicked. But when the user clicks the trash it also triggers a click event on the image. How can I stop the triggering of the image click when the trash is clicked?
Here is my code.
$("div.spaceEvent").off('click').on('click', function() {
scope.eventId = $(this).data('event-id');
//$("#registeredMemberContainer").html('');
scope.GetRegisteredMembersAsHost();
});
$("i.removeEvent").off('click').on('click', function() {
scope.eventId = $(this).data('event-id');
scope.spaceId = $(this).data('space-id');
var model = {};
model.eventId = scope.eventId;
model.spaceId = scope.spaceId;
// do other stuff here
});
<ul class="thumbnails" style="padding-left: 0px;">
#{ var listItems = count > 3 ? 3 : count; } #for (int j = 0; j
< listItems; j++) { var spaceEvent=M odel.YogaSpaceEvents.ElementAt(incrament++); <li class="col-sm-4" style="padding-left: 5px; padding-right: 5px;">
<div class="spaceEvent" data-event-id=#spaceEvent.YogaSpaceEventId>
<div class="thumbnail">
<div>
<img class="img-responsive" style="position: relative; left: 0; top: 0;" src="data:image/jpg;base64, #(Html.Raw(Convert.ToBase64String(spaceEvent.SpaceThumbnail43)))" alt="space image">
<i style="z-index: 200; position: absolute; top: 8px; right: 15px; color: whitesmoke;" class="fa fa-trash-o fa-2x removeEvent" data-event-id=#spaceEvent.YogaSpaceEventId data-space-id=#spaceEvent.YogaSpaceRefId data-container="body" data-toggle="popover"
data-trigger="hover" data-placement="top" data-content="Cancel event"></i>
</div>
<div class="caption" style="padding-top: 0px; padding-bottom: 0px;">
<h4 style="margin-top: 5px; margin-bottom: 5px;">#spaceEvent.Title</h4>
<p style="margin-bottom: 0px;"><span>#spaceEvent.EventDateTime.ToShortTimeString()</span><span> · </span><span>#YogaBandy2017.Models.General.EnumHelper.GetDisplayName(spaceEvent.Duration)</span></p>
<p style="margin-bottom: 0px;">#spaceEvent.StyleMain.ToString()</p>
<p class="teacher-container" style="margin-bottom: 0px;">Teacher: #(spaceEvent.IsTeacherRegistered ? spaceEvent.RegisteredTeacherName : "none")</p>
<p><span class="registered-container">Registered</span>: <span class="badge">#spaceEvent.RegisteredStudentCount/#spaceEvent.MaxSize</span></p>
</div>
</div>
</div>
</li>
count -= 1; }
</ul>
You should be invoking stopPropagation first thing in the click handler.
$("i.removeEvent").off('click').on('click', function(event) {
event.stopPropagation();
// do other stuff here
});
This behavior is the wanted and expected result. It's called event propagation or bubbeling.
You can avoid this by calling event.stopPropagation() inside the eventhandler:
$("i.removeEvent").off('click').on('click', function(evt) {
evt.stopPropagation(); // this avoids the event bubbeling / propagation
scope.eventId = $(this).data('event-id');
scope.spaceId = $(this).data('space-id');
var model = {};
model.eventId = scope.eventId;
model.spaceId = scope.spaceId;
// do other stuff here
});
In addition of the comment: What is the difference between event.stopPropagtion() and event.stopImmediatePropagation()?
The difference is the following:
<body>
<div>Some div Content
<i>Close</i>
</div>
</body>
<script>
$('div').on('click', function() {
console.log('div was clicked!');
});
$('i').on('click', function(evt) {
console.log('i was clicked! This message is from the first event handler!');
// one case
evt.stopPropagation();
/* Message in console:
i was clicked! This message is from the first event handler!
i was clicked! This message is from the second event handler!
*/
// other case:
evt.stopImmediatePropagation();
/* Message in console:
i was clicked! This message is from the first event handler!
*/
// "div was clicked!" will never read when i is clicked. It's only displayed if the div is clicked directly.
});
$('i').on('click', function() {
console.log('i was clicked! This message is from the second event handler!');
});
</script>
Related
I have multiple divs on the page with the class 'item' – I'd like to include a button within the div that when clicked will toggle append/remove the class 'zoom' on the 'item' div…
<div class="item">
<button class="zoomer"></button>
</div>
I've found plenty of code examples that target an id element, but struggling to find a solution that works with multiples of the same class element on one page.
Many thanks in advance!
You can use querySelectorAll to get all of the buttons and then you can use forEach so you can target the element's item parent.
// Get all the buttons
let zoomer_button = document.querySelectorAll('.zoomer');
// Loop through the buttons.
// Arrow function allows to pass the element
zoomer_button.forEach(button => {
// Add an event listener for a click on the button.
button.addEventListener('click', function(e) {
// the e is the event, and then you check what the target is, which is the button.
// then you can toggle a 'zoom' class on the parent 'item'
e.target.parentNode.classList.toggle('zoom');
});
});
.item.zoom {
background-color: blue;
}
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
If it's nested a layer deeper, you can use parentNode twice.
// Get all the buttons
let zoomer_button = document.querySelectorAll('.zoomer');
// Loop through the buttons.
// Arrow function allows to pass the element
zoomer_button.forEach(button => {
// Add an event listener for a click on the button.
button.addEventListener('click', function(e) {
// the e is the event, and then you check what the target is, which is the button.
// then you can toggle a 'zoom' class on the parent 'item'
e.target.parentNode.parentNode.classList.toggle('zoom');
});
});
.item.zoom {
background-color: blue;
}
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
You can use querySelectorAll and access each element with e.target
document.querySelectorAll('.item > .zoomer')
.forEach(elem => elem.addEventListener('click', e => {
e.target.classList.toggle('someClass')
}))
.someClass{
background:limegreen;
}
<div class="item">
<button class="zoomer">1</button>
</div>
<div class="item">
<button class="zoomer">2</button>
</div>
<div class="item">
<button class="zoomer">3</button>
</div>
<div class="item">
<button class="zoomer">4</button>
</div>
In the example below, are 7 <button>s that do various stuff -- details are commented in example.
// Render 7 <menu>/<button> combos
[...new Array(7)].forEach((item, index) => {
document.querySelector('main').insertAdjacentHTML('beforeend', `
<menu class="item${index}">
<button class="btn${index}">${index}</button>
</menu>`);
});
/*~~~~~~~~~~~~~~~~~~~~~~~.btn0*/
// Click <button> remove it's parent (which also removes the <button>)
document.querySelector('.btn0').onclick = function(e) {
this.parentElement.remove();
}
/*~~~~~~~~~~~~~~~~~~~~~~~.btn1*/
// Click <button> -- <button> is removed but it's contents is left behind
document.querySelector('.btn1').onclick = unWrap;
function unWrap(e) {
const clicked = e.target;
const parent = clicked.parentElement;
while (clicked.firstChild) {
parent.insertBefore(clicked.firstChild, clicked);
}
clicked.remove();
}
/*~~~~~~~~~~~~~~~~~~~~~.btn4-6*/
// Collect all tags with a class that starts with "btn"
const btns = document.querySelectorAll("[class^='btn']");
// Adding .target class to the last 2 <button>s
btns.forEach((btn, idx) => {
if (idx > 4) btn.classList.add('target')
});
/*~~~~~~~~~~~~~~~~~~~~~~~.btn2*/
// Target third <button> by index
/*
When <button> clicked, it's parent gets .hide class which is:
visibility:hidden which would normally hide the <button> as well, but
.btn2 has visibility explicitly set to visible
*/
btns[2].onclick = e => e.target.closest('menu').classList.toggle('hide');
/*~~~~~~~~~~~~~~~~~~~~~~~.btn3*/
/*
Everytime the <button> is clicked, a copy of itself is made and the
clones also have this ability as well
*/
btns[3].addEventListener('click', copySelf);
function copySelf(e) {
let dupe = e.target.cloneNode(true);
e.target.parentElement.append(dupe);
dupe.onclick = copySelf;
}
/*~~~~~~~~~~~~~~~~~~~~~.btn4-6*/
/*
The click event is bound to the parent/ancestor tag <section>
Any click to any <button> will trigger the event handler.
.btn4, .btn5, and .btn6 all react in a specific manner because
the event handler, delegateClick(e) is using flow control statements and
specific criteria.
*/
document.querySelector('main').onclick = delegateClick;
let armed = false;
function delegateClick(e) {
const clicked = e.target;
if (clicked.matches('button') && !armed) {
clicked.classList.add('armed');
armed = true;
return;
}
if (clicked.matches('.armed.target') && armed) {
clicked.parentElement.style.cssText = `font-size: 5rem; margin: 0`
clicked.replaceWith(`💥`);
return;
}
if (clicked.matches('.target') && armed) {
clicked.classList.add('armed');
return;
}
if (clicked.matches('.armed') && armed) {
clicked.classList.remove('armed');
armed = false;
}
}
menu {
outline: dashed red 1px;
}
.hide {
visibility: hidden;
}
.btn2 {
visibility: visible
}
.armed {
animation: warning 1s linear infinite;
}
#keyframes warning {
50% {
opacity: 0;
}
}
.target.armed {
background: red;
color: white;
}
button {
font: inherit;
cursor: pointer;
}
<main></main>
To summarise the code, I have buttons that display different tabs when pressed. Within the tabs, there are more buttons that change the color of some div elements and only one tab can be opened at a time. All this works as it should for the most part.
All buttons had been using focus but I wanted to replace it with javascript so that the selection will be retained when clicking on different elements. No tabs should be visible if the current opened tab button is pressed like it does when the code first runs.
I have had a few issues trying to get this to work properly. At the moment, the color buttons remain clicked. When tab toggles, the tab button loses selection and the tab div doesn't close when I click on the current selected tab's button.
https://jsfiddle.net/gkde169x/4/
<button class="tabButton" onclick="toggle_tab('tabOne');">Tab One</button>
<button class="tabButton" onclick="toggle_tab('tabTwo');">Tab Two</button>
<div id="tabOne" class="clickedTab" style="display: none;">
<br><br>
<div id="paletteOne">
<button class="paletteButton" style="background-color: blue"></button>
<button class="paletteButton" style="background-color: red;"></button>
<button class="paletteButton" style="background-color: yellow;"></button>
<button class="paletteButton" style="background-color: Green;"></button>
<button class="paletteButton" style="background-color: Orange;"></button>
<button class="paletteButton" style="background-color: white;"></button>
</div>
</div>
<div id="tabTwo" class="clickedTab" style="display: none;">
<br><br>
<div id="paletteTwo">
<button class="paletteButton" style="background-color: blue"></button>
<button class="paletteButton" style="background-color: red;"></button>
<button class="paletteButton" style="background-color: yellow;"></button>
<button class="paletteButton" style="background-color: Green;"></button>
<button class="paletteButton" style="background-color: Orange;"></button>
<button class="paletteButton" style="background-color: white;"></button>
</div>
</div>
<div id="change1"></div>
<div id="change2"></div>
<script type="text/javascript">
const divOne = document.getElementById('change1');
const divTwo = document.getElementById('change2');
document.querySelectorAll('#paletteOne button').forEach(function (el) {
el.addEventListener('click', function () {
divOne.style.backgroundColor = el.style.backgroundColor;
el.className = "paletteSelect";
});
});
document.querySelectorAll('#paletteTwo button').forEach(function (el) {
el.addEventListener('click', function () {
divTwo.style.backgroundColor = el.style.backgroundColor;
el.className = "paletteSelect";
});
});
function toggle_tab(id) {
const target = document.getElementById(id);
if (!target) {
return;
}
// Hide unselected tabs
const tabs = document.querySelectorAll('.clickedTab');
for (const tab of tabs) {
tab.style.display = 'none';
}
// Show current tab
target.style.display = 'block';
}
What's the best way to accommodate this in my code?
to unclick the color button I would do something like this, (with each click check for clicked buttons and unclick)
const pal = document.getElementById('paletteOne')
pal.addEventListener('click', function(e) {
document.querySelectorAll('#paletteOne button').forEach(function(el) {
el.className = "paletteButton"});
if(e.target.className==="paletteButton"){
divOne.style.backgroundColor = e.target.style.backgroundColor;
e.target.className = "paletteSelect";
}
});
to hide selected tab when clicked on
const tabs = document.querySelectorAll('.clickedTab');
for (const tab of tabs) {
if(tab!== target || target.style.display === 'block'){
tab.style.display = 'none';
}else{
target.style.display = 'block';}
}
obviously these things can be done differently, I'm just working off your code...
In your javascript
function toggle_tab(id) {
const target = document.getElementById(id);
if (!target) {
return;
}
const tabShown = document.querySelectorAll('.show')
tabShown.forEach((tab) => {
if(target != tab) tab.classList.remove('show')
})
target.classList.toggle('show');
}
Also in your CSS use classes. (You can create one class and give it to both of them since they have so many styles in common and use tabTwo and tabOne classes only for differences.)
.tabContainer {/*here use this class, give this to both tabs*/
position: absolute;
margin-top: 38px;
height: 100px;
width: 100px;
padding-left: 50px;
padding-bottom: 50px;
border-style: solid;
border-color: black;
background: white;
display:none;/*here*/
}
.tabTwo {/*here use class*/
margin-left: 20px;
}
.show{
display:block;
}
How can I determine if a bubbling event was already handled before.
I want to handle a click event that was performed on a .card element. The event handler should trigger a click event on the first link in that card. If the user clicks on the second link in that card, the card's event handler should do nothing.
So I want to find a way, if any event handler (built-in or custom, including those that I have no control over) was called before.
Edit
This is not a question about a specific issue I have, but a general question on DOM event handling.
Edit 2
For popular demand (1 comment :) ) I'll add some example code.
This example will not fully work, as there are restrictions to links in SO snippets. I have a fully working example on Codepen
;
(function () {
var selectors = []
var subs = []
function triggerMouseEvent(
event,
target,
options
) {
options = Object.assign({
view: window,
bubbles: true,
cancelable: true
}, options)
const e = new MouseEvent(event, options)
setTimeout(function () {
target.dispatchEvent(e)
}, 10)
}
function delegateClick(selector, subSelectors) {
var selector = selectors.push(selector) - 1;
var sub = subs.push(subSelectors) - 1;
return {selector,sub}
}
function undelegateClick({selector, sub}) {
selectors.splice(selector, 1);
subs.splice(sub, 1);
}
function evnt(e) {
selectors.forEach(function (selector, index) {
if (e.target.closest(subs[index].join(','))) {return}
var s = e.target.closest(selector)
if (!s) {return}
var selEl
subs[index].find(function (sub) {
return selEl = s.querySelector(sub)
})
if (selEl && e.target !== selEl) {
triggerMouseEvent(e.type, selEl, {
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey
})
}
})
}
document.addEventListener('click', evnt)
// document.addEventListener('mouseover', evnt)
window.delegateClick = delegateClick
window.undelegateClick = undelegateClick
})();
dup = delegateClick('.card', ['a.act-on','button.act-on'])
.card {
margin: 20px;
border: 1px solid;
padding: 20px;
}
.card:hover {
background-color: gold;
cursor: pointer;
}
body {
display: flex;
}
<div class="card">
<h1>Click this card</h1>
<p>The action of the following button will be performed</p>
<button class="act-on" onClick="alert('Button pressed!')">Button!</button>
</div>
<div class="card">
<h1>Click this card</h1>
<p>The action of the following link will be performed <p>
<a class="act-on" href="https://example.org" target="_blank">example.org</a>
</div>
<div class="card">
<h1>Click this card</h1>
<p>The action of the following link will be performed</p>
<p>The link is preferred to the button</p>
<button class="act-on" onClick="alert('Button pressed!')">Button!</button>
<a class="act-on" href="https://example.org" target="_blank">example.org</a>
</div>
<div class="card" style="background-color: pink; border-color:red">
<h1>Click this card</h1>
<p><strong>If this button is pressed, the action of the link should not be done</strong></p>
<button class="dont-act-on" onClick="alert('Button pressed!')">Button!</button>
<a class="act-on" href="https://example.org" target="_blank">example.org</a>
</div>
I'm working on a site that has buttons that are generated dynamically.
I'm using jQuery to toggle classes of hidden elements $('.toggle-button').on('click') (i.e. off-canvas cart, sidebar, etc.) based on data attributes
I'm using trigger('click') for the dynamic buttons and passing data through to trigger the correct hidden element. The problem is trigger() is bubbling to other buttons that have class toggle-button or that's what I think the problem is...
I've tried event.stopPropagation() but it doesn't seem to be working.
Here's a simplified version of the code I'm working with.
jQuery(document).ready(function($){
$('.toggle-button').on('click', function(event, triggerData){
console.log('toggle button triggered');
toggleClass = '';
targetSelector = '';
targetElement = '';
if ( !triggerData ) {
toggleClass = $(this).attr('data-toggle');
targetSelector = $(this).attr('data-target');
} else {
toggleClass = triggerData.toggleClass;
targetSelector = triggerData.targetSelector;
}
targetElement = $(targetSelector);
targetElement.toggleClass(toggleClass);
});
$(document).on('click', '.view-cart', function(event){
event.preventDefault();
console.log('view cart button clicked');
$('.toggle-button').trigger('click', {
toggleClass : 'show',
targetSelector : '.cart'
});
});
});
jQuery(document).ready(function($) {
$('.toggle-button').on('click', function(event, triggerData) {
console.log('toggle button triggered');
toggleClass = '';
targetSelector = '';
targetElement = '';
if (!triggerData) {
toggleClass = $(this).attr('data-toggle');
targetSelector = $(this).attr('data-target');
} else {
toggleClass = triggerData.toggleClass;
targetSelector = triggerData.targetSelector;
}
targetElement = $(targetSelector);
targetElement.toggleClass(toggleClass);
});
$(document).on('click', '.view-cart', function(event) {
event.preventDefault();
console.log('view cart button clicked');
$('.toggle-button').trigger('click', {
toggleClass: 'show',
targetSelector: '.cart'
});
});
});
.cart,
.info {
display: block;
padding: 30px;
width: 200px;
border: 1px solid #000;
visibility: hidden;
}
.cart.show,
.info.show {
visibility: visible;
}
.product {
display: inline-block;
width: 150px;
margin: 15px;
background: #e3e3e3;
text-align: center;
}
.product a {
display: block;
padding: 15px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button class="toggle-button" data-toggle="show" data-target=".cart">View Cart</button>
<button class="toggle-button" data-toggle="show" data-target=".info">View Info</button>
<hr>
<div class="product">
<img src="https://via.placeholder.com/150">
View Cart
</div>
<div class="product">
<img src="https://via.placeholder.com/150">
View Cart
</div>
<div class="product">
<img src="https://via.placeholder.com/150">
View Cart
</div>
<div class="product">
<img src="https://via.placeholder.com/150">
View Cart
</div>
<div class="cart">
<span>This is your cart</span>
</div>
<div class="info">
<span>This is additional info</span>
</div>
</body>
</html>
there are two button with class toggle-button ...whose click event is being triggered correctly - while the problem is, that probably only one of them should be triggered. either select the element to click by a combined class attribute or by id attribute. those events are not bubbling at all, the selector just matches two elements - and that's why it subsequently clicks onto two elements.
Your code has two .toggle-button element, so $('.toggle-button').trigger() is running two times.
I recommend to name different class names of these basically.
But there is a other way not to do so, you can use :first selector as shown below.
https://api.jquery.com/first-selector/
jQuery(document).ready(function($){
$('.toggle-button').on('click', function(event, triggerData){
console.log('toggle button triggered');
toggleClass = '';
targetSelector = '';
targetElement = '';
if ( !triggerData ) {
toggleClass = $(this).attr('data-toggle');
targetSelector = $(this).attr('data-target');
} else {
toggleClass = triggerData.toggleClass;
targetSelector = triggerData.targetSelector;
}
targetElement = $(targetSelector);
targetElement.toggleClass(toggleClass);
});
$(document).on('click', '.view-cart', function(event){
event.preventDefault();
console.log('view cart button clicked');
$('.toggle-button:first').trigger('click', {
toggleClass : 'show',
targetSelector : '.cart'
});
});
});
I have some divs that appear on click of a link, but i am trying to make it so that when you click on a 2nd link to popup, any open ones will be closed before the new one opens. there should only be one open at a time.
the js...
<script>
$.fn.slideFadeToggle = function (easing, callback) {
return this.animate({
opacity: 'toggle',
width: 'toggle'
}, "fast", easing, callback);
};
$(function () {
function select($link) {
$link.addClass('selected');
$($link.attr('href')).slideFadeToggle(function () {});
}
function deselect($link) {
$($link.attr('href')).slideFadeToggle(function () {
$link.removeClass('selected');
});
}
$('.contact').click(function () {
var $link = $(this);
if ($link.hasClass('selected')) {
deselect($link);
} else {
select($link);
}
return false;
});
$('.close').live('click', function () {
deselect();
return false;
});
});
</script>
the divs...
<div id='did_{$page_trackid}' class='arrow_box pop_{$page_trackid}' style=''> <img src='".$info4['Image']."' class='subtext_img'>
<h2 class='subtext'><a href='http://www.xxxxxxx.co.uk/dnb/".$info2['username']."'>".$info2['username']."</a></h2>
<p class='subtext'>".$info3['user_title']."</p>
<p class='subtext'><a href='".$info3['website_link']."' target='_blank'>".$info3['website_link']."</a>
</p>
</div>
<div id='did_2_{$page_trackid}' class='arrow_box2 pop_stats_{$page_trackid}' style=''>
<h2 class='subtext'>Stats</h2><br />
<p class='subtext'>Plays: 1m <br />
Downloads: 527, 046
</p>
</div>
the links...
<div style='position: absolute; z-index: 2; padding-top: 30px; padding-left: 699px;'>
<a href='#did_{$page_trackid}' class='contact' ><img style='height: 20px;' alt='Posted by' src='http://www.xxxxxxxxxx.co.uk/play1/skin/user-profile2.png' style=''></a>
</div>
<div style='position: absolute; z-index: 1; width: 20px; height: 20px; padding-top: 50px; padding-left: 699px;'>
<a href='#did_2_{$page_trackid}' class='contact'><img style='height: 20px;' alt='Track stats' src='http://www.xxxxxxxx.co.uk/play1/skin/stats.png' style=''></a>
</div>
I have tried replacing the first function with
function select($link) {
$link.addClass('selected');
$('.arrow_box:visible').slideFadeToggle(function () {});
$($link.attr('href')).slideFadeToggle(function () {});
}
but that bugs out, with one pop over lapping the other. I have 2 classes for the divs(1 for each) so i attempted to add
$('.arrow_box2:visible').slideFadeToggle(function () {});
but that too doesnt work.
Am i going about it the right way to close any open arrow_box or arrow_box2 when clicking a link to open a new pop up??
thanks
I copied your html and js into a jsfiddle and modified the select method. Try it out here:
http://jsfiddle.net/mchail/wHyfK/1/
I believe this now does what you asked for. The key is to toggle any shown panes (to hide them) before toggling the new "selected" pane (to show it).
Hope this helps.