Nested Click Event bubbling issue - javascript

I am working with a nested click event (there is a large array of images, some with children images, some without).
Onclick for all of the images (children, parent and non-parent), they should display in a diff css div, and then remove themselves if clicked again, indefinitely.
So far this code works for the parent and non-parent images, but bubbling is making the children images display the parent images instead.
If I return false to stop the bubbling before the children function, it will break the functionality for the parent and non-parent images that are further down in the array (the children are in a new popup css, not a list), but if I don't stop the bubbling, the nested click event won't run.
Anyone see a good way around this mess?
// Click an image, and it will change it in the demo css class panel
$('.item-button').data('status','not_clicked');
$('.item-button').click(function(event) {
var layerID = $(this).attr('data-layer'); //these do not correspond to actual layers of nesting.
var itemID = $(this).attr('data-item');
var itemmulti = $(this).attr('data-multi'); //this value def works to recognize parent from other non-parent items (not children).
var clayerID = $(this).attr('data-clayer');
var citemID = $(this).attr('data-citem'); //child item 1.
if(( $(this).data('status') == 'clicked') || itemID == 0) {
var imageSrc = $(this).parent().find('img').attr('id');
$(layerID).attr('src', imageSrc);
} else {
$(this).data('status','clicked');
var imageSrc = $(this).parent().find('img').attr('id');
$(layerID).attr('src', imageSrc);
if (itemmulti != 0) {
//begin headache.
document.getElementById('child-' + itemID).style.display = "inline-block";
//this is where a separate click function for children items begins.
$('.item-sub1').data('status','unclicked');
$('.item-sub1').click(function(event) {
if(( $(this).data('status') == 'click') || citemID == 0) {
var imageSrc = $(this).parent().find('img').attr('id');
$(selector).attr('src', imageSrc);
} else {
$(this).data('status','click');
var imageSrc = $(this).parent().find('img').attr('id');
$(clayerID).attr('src', imageSrc);
}
return false;
});
}
}
});
<img button title id alt />
<input radio id="layer-{ID}-{item.ID}-radio" name="layer-{ID}" value="{item.ID}" class="item-button" data-multi="{item.PARENT}" data-layer="{ID}" data-item="{item.ID}" />
<!-- IF item.CHILD1 != 0 -->
<div id="child-{item.ID}" style="width: 300px; position: absolute; display: none;">
<img button here title data-layer="{item.CLAYER1}" data-item="{item.CHILD1}">
<input radio id="layer-{item.CLAYER1}-{item.CHILD1}-radio" name="layer-{item.CLAYER1}" value="{item.CHILD1}" style="display: none;" class="item-sub1" data-clayer="{item.CLAYER1}" data-citem="{item.CHILD1}" />

I think what you need to do to prevent the event from bubbling up the DOM tree and preventing any parent handlers from being notified of the event is to use stopPropagation() method.
$('.item-button').click(function(event) {
event.stopPropagation();
// your code...
For more info:
1. jquery
2. javascript

Related

Is there a way to add an event listener on two separate divs and be able to know when the user clicked outside of one by itself?

I am writing Javascript code that shows pictures on a category based on the ones the user clicked on but will show all categories when clicked outside the filtered pictures. Yet, I need the same code to work on separate divs independently, not the whole dom.
Attaching the event listener to the document works except, as you may have guessed, it doesn't work on two divs independently. When I attach it to a reference dom, let's day the dom id, it works but it doesn't know when the user clicked outside
document.addEventListener("click", (evt) => {
//get an array of all the div with "column" class
var imgElements = imgGrid.getElementsByClassName("column");
var w, x;
var y, z;
let targetElement = evt.target; // clicked element
do {
//itirate through all the divs we got the reference for
for (var i = 0; i < imgElements.length; i++) {
//check if we click on any of those divs
if (targetElement.className == imgElements[i].className) {
//we clicked on a div
//let's get the class name we want to filter by
w = imgElements[i].className;
x = w.split(' ');
console.log("You clicked on a: " + x[1]);
//we're not done //let's go itirate those divs once more
//but this time for everyone that don't have a class that
//matched our filter class we hide it, else we show it
for (var i = 0; i < imgElements.length; i++) {
y = imgElements[i].className;
z = y.split(' ');
if (z[1] != x[1]) {
addClass(imgElements[i], "hidden");
} else {
removeClass(imgElements[i], "hidden");
}
}
return;
}
}
// Go up the DOM.
targetElement = targetElement.parentNode;
} while (targetElement);
console.log("You clicked outside");
//other useful things being done here
});
Instead of "document.addEventListener" I will say DomId.addEventlistner and expect it to know to when I clicked inside and outside of each dom reference.
You can give DIVs that should be 'clickable' an unique class name. That way your logic doesn't have to know each 'inside' DIVs id.
function handleClick(e) {
if (e.target.className != "clickableDiv") {
console.log("clicked outside");
} else {
var element = e.target.id;
console.log(element + " clicked");
}
}
document.addEventListener("click", handleClick);
<div id="container1" class="clickableDiv">
DIV1
</div>
<div id="container2" class="clickableDiv">
DIV2
</div>
<div id="container3">
I'm not clickable
</div>

jQuery Blur() not working with click() function

I am trying to make a tooltip with content disappear when I click outside it, but tried solutions to different posts and none of those solutions work for me.
I think the problem is how I am triggering the tooltip, but is the only way I know, here is the code I have in my js and my html markup.
The tooltip fires well, but it doesn't work with the blur event in any way I tried.
$(document).ready(function() {
function tooltover(target_item){
$(target_item + '> a').click(function(){
$('.tiptover_box').focus();
var pos = $(this).position();
var width = $(this).outerWidth();
var boxw = $(this).next().outerWidth();
var boxh = $(this).next().outerHeight();
$(this).next().css('left', (pos.left - ((boxw/2)-(width/2))));
$(this).next().css('top', (pos.top - (boxh+5)));
$(this).next().addClass('tiptover_show');
$(this).next().delay(5).queue(function(){
$(this).addClass('tt-in');
$(this).dequeue();
});
});
$('.tiptover_box').blur( function(){
$('.tiptover_box').removeClass('tiptover_show tt-in');
});
} tooltover('.tiptover');
});
And HTML
<div class="tiptover">
<a>Link Test</a>
<div class="tiptover_box" style="background-image: url(content/prod_0002.jpg);">
<div>Description.</div>
</div>
</div>
A different approach would be to not focus on the div itself, but focus on the rest of the DOM.
In this solution given by prc322 on a similar question he used the mouseup event on the document itself:
$(document).mouseup(function (e)
{
var container = $(".tiptover_box");
if (!container.is(e.target) // if the target of the click isn't the container...
&& container.has(e.target).length === 0) // ... nor a descendant of the container
{
container.removeClass('tiptover_show tt-in');
}
});
(edited by me for relevance)
Now your tooltip will be "closed" whenever a mouseup event fired on any element that is not the .tiptover_box or any of its descendants.
Instead of trying to trigger the blur event of a div (which is not as reliable as blurring an input field), you can do what you say you want - when anything is clicked, you hide the tooltip:
$('body').click(function(e){
var tooltipContainer = $('.tiptover_box');
var clickTarget = e.target;
if(!tooltipContainer.is(clickTarget)
&& tooltipContainer.has(clickTarget).length === 0){
tooltipContainer.removeClass('tiptover_show tt-in');
}
});
edit: added test for container and its children, basically same as the answer from Bob.

onclick of button from paginated load not triggering

I have a procedural feed within my website which loads in data every time the user scrolls to the bottom of the page; the usual stuff.
The pagination itself works fine; however, the clicking of buttons which utilize JavaScript do not work. At all.
This is the JavaScript trigger for the buttons which is not working:
var modalTrigger = document.querySelectorAll(".ts-modal__trigger"), // the buttons
modal;
document.onclick = function(e){ // document on click
for(var i = 0; i < modalTrigger.length; i++){ // iterate through each button
if(e.target == modalTrigger[i]){ // if target clicked was button
e.stopPropagation(); // stop dom event from bubbling
modal = document.getElementById(modalTrigger[i].getAttribute("data-activemodal"));
document.body.style.overflow = "hidden"; // disable scroll on page
modal.style.display = "block"; // display modal
}
}
}
As far as I'm aware, this should be working (with the use of stopPropagation()), but alas, it does not.
I was going to use Inline JS, but I feel like it's extremely unnecessary and could be done it just a couple of lines of separate JavaScript, instead of adding extra HTML into the mix for no reason.
So any, and all help is appreciated,
Thanks. :)
Fiddle: https://jsfiddle.net/xhp4cesr/
EDIT: I noticed that after removing the span within the button, it would work, but as soon as it was added back, it would not.
One way you can do this is to remove e.stopPropogation and also target the children
var modalTrigger = document.querySelectorAll(".ts-modal__trigger"), // the buttons
modal;
document.onclick = function(e) { // document on click
for (var i = 0; i < modalTrigger.length; i++) { // iterate through each button
if (e.target == modalTrigger[i] || modalTrigger[i].children) { /* <- added children as target */
modal = modalTrigger[i].getAttribute("data-activemodal");
console.log(modal);
}
}
}
a {
background: red;
padding: 40px;
}
span {
color: yellow;
}
<a class="ts-modal__trigger" data-activemodal="ts-main-feed_status-comments-overlay">
<span>11</span>
</a>

Attaching eventListener to dynamic elements in Javascript

I'm making a todo list and I have li and button tags added dynamically when adding a new list item. The button is an x which is supposed to remove the list item. I have tried several things but can't figure out how to make an eventListener for each individual x button and remove the corresponding list item when it is clicked.
The renderTodos function is where all of the dynamically added content is created. I have a data-index set to each button in which I was trying to use to access each button to attach an eventListener on each dynamic button, but I wasn't sure how to implement that. From what I have read there should be a way to do this using the currentTarget or target of the event but I don't understand how that works.
var input = document.querySelector('input[name=todoItem]'),
btnAdd = document.querySelector('button[name=add]'),
btnClear = document.querySelector('button[name=clear]'),
list = document.querySelector('.todo'),
storeList = [];
function renderTodos(){
var el = document.createElement('li'),
x = document.createElement('button');
listLength = storeList.length;
//Set text for remove button
x.innerHTML = 'x';
for(var i = 0; i < listLength; i++){
el.innerHTML = storeList[i];
list.appendChild(el);
x.setAttribute('data-index', i);
el.appendChild(x);
}
// check for correct data-index property on x button
}
function addTodos(){
storeList.push(input.value);
// Check that input is getting pushed to list array
console.log(storeList);
renderTodos();
}
function clearList(){
// make list empty
list.innerHTML = '';
storeList.splice(0, storeList.length);
//render empty list
renderTodos();
//Check that list array is empty
console.log(storeList);
}
btnAdd.addEventListener('click', addTodos);
btnClear.addEventListener('click', clearList);
Everything else on the list works so far I just can't figure out how to implement this eventListener.
One simple example can be
//a click hadler is added to #mylist which is already present in the dom
document.querySelector('#mylist').addEventListener('click', function(e) {
//assuming that the the `x` is in a span and it is the only span in the `li` we check for that, we can improve this check more to make sure we have actually clicked on the delete button
if (e.target.tagName == 'SPAN') {
//if so then since we know the structure we can delete the parent node of the target which is the span element
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
}, false);
//kindly forgive the use of jQuery here
for (var i = 0; i < 10; i++) {
$('<li />', {
text: i
}).append('<span class="x">X</span>').appendTo('#mylist');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul id="mylist"></ul>
This is a very basic implementation of event delegation, where the actual event is bound to an ancestor element but then we use the actual event target to determine whether to act on it. We can improve the if condition to test for an class for any other attribute!!!
You can add a listener to each button using something like:
x.innerHTML = '';
x.onclick = function(){
var node = this.parentNode;
node.parentNode.removeChild(node);
};
Or you can keep the renderTodos code as it is and delegate the remove to the parent UL:
// Add the listener
list.addEventListener('click', removeItem);
// The listener function
function removeItem(event) {
var node = event.target;
// Check that the click came from an X button
// better to check against a class name though
if (node.tagName &&
node.tagName.toLowerCase() == 'button' &&
node.innerHTML == 'x') {
node = node.parentNode;
node.parentNode.removeChild(node);
}
}
basically what you want to do is add an event on the parent container and wait for the event to bubble up and identify if the event originating is from your x mark and if it is then trigger the callback function.. This is the concept I think most of the libraries use..
Or use a library like jQuery, why solve a problem that has already been solved by others.

Erratic mouseover behavior with nested items inside mouseover layer

So let's say we have:
A container for everything
A baseDiv inside that container
//let's create a base layer
var container = document.getElementById('container')
var baseDiv = document.createElement('div')
baseDiv.id = 'baseDiv'
baseDiv.innerText = 'this is the base div'
baseDiv.addEventListener('mouseover', createLayer)
container.appendChild(baseDiv)
When the user mouses over:
A layerOnTop, of the same size is put on top of the baseDiv.
When the user mouses out:
The layerOnTop is removed.
function createLayer(){
console.log('creating layer')
layerOnTop = document.createElement('div')
layerOnTop.id = 'layerOnTop'
layerOnTop.addEventListener('mouseout',
function(){
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})
container.appendChild(layerOnTop) }
Simple and works great.
However, when layerOnTop contains elements as well (buttons, inputs), the behavior gets very erratic and starts flicking as you're technically exiting the layerOnTop.
//it contains two textareas
layerOnTop.appendChild(document.createElement('textarea'))
layerOnTop.appendChild(document.createElement('textarea'))
I wish I could use mouseenter but it doesn't seem to be supported by Chrome.
Here's my jsfiddle: http://jsfiddle.net/DjRBP/
How can I stop this? I wish I could merge the textareas and layerOnTop into one large mouseover-handling conglomerate.
You need to check in your mouse out event that it's actually leaving the element. Change your mouseout function to:
function(event) {
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
// We're not actually leaving the parent node so don't remove layer
return;
}
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})

Categories

Resources