Determine if an event was handled before - javascript

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>

Related

Click button to toggle class of parent element - pure javascript

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>

Make current selections visible through Javascript

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

jQuery .not() and :not with new classes? [duplicate]

This question already has answers here:
jQuery click event not working after adding class
(7 answers)
Closed 2 years ago.
I have some card to flip and check if one is equal to another (memory game).
If I flip the card, I don't want that is possible to click and run function if I click on the same card (that is .flipped) or on another that is flipped. But jQuery .not() and :not not working. Maybe I must read another time the DOM after .toggleClass?
$(".card:not('.flipped')").on("click", function () {
$(this).toggleClass("flipped");
if (first) {
firstCard = $(this).attr("game");
first = false;
} else {
secondCard = $(this).attr("game");
first = true;
checkGame();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
The code is binding the events when it is called. So whatver the classes are at that moment in time, is what it finds and binds the event.
So you need to check for the class inside of the method and exit it
$(".card").on("click", function() {
var card = $(this);
if (card.hasClass("flipped")) return;
console.log(this);
card.addClass("flipped");
});
.wrapper {
display: flex;
}
.wrapper > .card {
flex: 1;
text-align: center;
line-height: 50px;
font-size: 30px;
}
.card.flipped {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
<div class="card">5</div>
<div class="card">6</div>
<div class="card">7</div>
<div class="card">8</div>
</div>
Other option is using event delegation where you bind the event to the parent and element and have jQuery do the checking if the class is added yet.
$(".wrapper").on("click", ".card:not('.flipped')", function() {
console.log(this);
var card = $(this);
card.addClass("flipped");
});
.wrapper {
display: flex;
}
.wrapper > .card {
flex: 1;
text-align: center;
line-height: 50px;
font-size: 30px;
}
.card.flipped {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
<div class="card">5</div>
<div class="card">6</div>
<div class="card">7</div>
<div class="card">8</div>
</div>
Apply the click event to all cards.
On click, check if the card is flipped. If it is, do nothing.
Cache your jQuery objects.
$(".card").on("click", function() {
const $card = $(this);
if ($card.hasClass("flipped")) return;
$card.toggleClass("flipped");
// .........
});
you have to check it inside your event 'click'. if the card has class 'flipped' break it
$(".card").on("click", function () {
if($(this).hasClass('.flipped')) return;
$(this).toggleClass("flipped");
if (first) {
firstCard = $(this).attr("game");
first = false;
} else {
secondCard = $(this).attr("game");
first = true;
checkGame();
}
});

Javascript on click event for multiple buttons with same class

I have a few buttons across a site I am building, certain buttons have one class while others have another. What I am trying to do is find the best way to find the clicked button without having an event listener for each individual button. I have come up with the below 2 for loops to find all the buttons with class button-1 and class button-2. Being fairly new to javascript i just don't want to get into bad habits so would appreciate any advice on the best way to achieve this.
<section>
<div class="button--1"></div>
<div class="button--1"></div>
<div class="button--2"></div>
</section>
<section>
<div class="button--2"></div>
<div class="button--1"></div>
<div class="button--2"></div>
</section>
var button1 = document.querySelectorAll('.button--1');
var button2 = document.querySelectorAll('.button--2');
for (var a = 0; a < button1.length; a++) {
button1[a].addEventListener('click',function(){
//do something
});
}
for (var b = 0; b < button2.length; b++) {
button1[b].addEventListener('click',function(){
//do something
});
}
If you plan to have multiple other classes like button--3, …4 … …15,
You must want to target all div elements which class starts (^=) with "button":
(Note that you can do it in the CSS too!)
var allButtons = document.querySelectorAll('div[class^=button]');
console.log("Found", allButtons.length, "div which class starts with “button”.");
for (var i = 0; i < allButtons.length; i++) {
allButtons[i].addEventListener('click', function() {
console.clear();
console.log("You clicked:", this.innerHTML);
});
}
/* Some styling */
section {
margin: 8px 0;
border: 1px solid gray;
}
section div {
border: 1px solid lightgray;
display: inline-block;
margin-left: 8px;
padding: 4px 8px;
width: 30px;
}
section div[class^=button] {
background: lightgray;
cursor: pointer;
}
<span>You can click on the buttons:</span>
<section>
<div class="button--1">s1-1</div>
<div class="button--2">s1-2</div>
<div class="button--3">s1-3</div>
<div class="button--4">s1-4</div>
</section>
<section>
<div class="button--1">s2-1</div>
<div class="button--2">s2-2</div>
<div class="button--3">s2-3</div>
<div class="button--4">s2-4</div>
</section>
<section>
<div class="not-a-button">not1</div>
<div class="not-a-button">not2</div>
<div class="not-a-button">not3</div>
<div class="not-a-button">not4</div>
</section>
Hope it helps.
Try using event delegation
(function() {
document.body.addEventListener("click", clickButtons);
// ^ one handler for all clicks
function clickButtons(evt) {
const from = evt.target;
console.clear();
if (!from.className || !/button--\d/i.test(from.className)) { return; }
// ^check if the element clicked is one of the elements you want to handle
// if it's not one of the 'buttons', do nothing
console.log("you clicked " + from.classList);
}
}())
.button--1:before,
.button--2:before {
content: 'BTTN['attr(class)']';
}
.button--1,
.button--2 {
border: 1px solid #999;
background: #eee;
width: 220px;
padding: 3px;
text-align: center;
}
<section>
<div class="b1 button--1 section1"></div>
<div class="b2 button--1 section1"></div>
<div class="b3 button--2 section1"></div>
</section>
<section>
<div class="b4 button--2 section2"></div>
<div class="b5 button--1 section2"></div>
<div class="b6 button--2 section2"></div>
</section>
You can use multiple selectors in the string of querySelctorAll() by separating them with a ,
var button1 = document.querySelectorAll('.button--1');
var button2 = document.querySelectorAll('.button--2');
var allButtons = document.querySelectorAll('.button--1, .button--2');
console.log(button1.length);
console.log(button2.length);
console.log(allButtons.length);
<section>
<div class="button--1"></div>
<div class="button--1"></div>
<div class="button--2"></div>
</section>
<section>
<div class="button--2"></div>
<div class="button--1"></div>
<div class="button--2"></div>
</section>
My suggestion is to use jQuery so that you can do it something like this:
$(document).on('click', '.button--1', function() {
// Do something
});
$(document).on('click', '.button--1', function() {
// Do something
})
But a clean approach for pure Javascript is to create a function that binds a callback for the event.
function bindEvent(callback, eventType, targets) {
targets.forEach(function(target) {
target.addEventListener(eventType, callback);
});
};
var button1 = document.querySelectorAll('.button--1');
var button2 = document.querySelectorAll('.button--2');
bindEvent(function() {
// do something
}, 'click', button1);
bindEvent(function() {
// do something
}, 'click', button2);
The click event is fired when a pointing device button (usually a mouse's primary button) is pressed and released on a single element.
This documentation will help you to understand how it works MDN - Click event

clicking on overlay element triggers click of underlay element

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>

Categories

Resources