With below code I'm trying to click a tag click event from the parent li tag. But it gives me this error:
Why I want to do this:
When I click the PHP PDO link we need to cursor move in to that text not the li tag. I'm trying to fix it using this. But I know we can call that href from the li click event by getting the a href attribute and set it into the window.location.href. But still trying to trigger the a href click event when I click the li tag.
HTML
<li class="tags" style="cursor: pointer;">
<a class="link_em" href="?l=1" id="1">List1</a>
</li>
Jquery:
$('li.tags').on('click', function (e) {
$(this).children('a').click();
return false;
});
Error:
I got this error when I use above code.
Uncaught RangeError: Maximum call stack size exceeded
UI:
https://jsfiddle.net/k92dep45/
Edited:
I done this ugly thing, but I am still looking proper solution:
$('li.tags a').on('click', function (e) {
e.stopPropagation();
});
$('li.tags').on('click', function (e) {
window.location.href = $(this).children('a.link_em').attr('href');
e.preventDefault();
});
You can use below version for the above code
$('li.tags').on('click', 'a', function (e) {
// Do your stuff.
e.preventDefault();
window.location.href= thishref;
});
Here the second argument is the children 'a' in the click event of jQuery.
The problem is that you keep calling the same element which leads to the Maximum call stack size exceeded.
Look at the below example.
function foo(){
foo();
}
foo();
In the above code, we are calling foo() again and again and it will also produce the same result.
You need to add event.stopPropagation() on a element and prevent event "bubbling" otherwise you create infinite loop and that is why you get that error.
$('li.tags').on('click', function() {
$(this).children('a').click();
});
$('li.tags a').click(function(e) {
e.stopPropagation()
e.preventDefault() // This is just for demo
console.log($(this).text())
})
a {
border: 1px solid black;
}
li {
padding: 50px;
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="tags" style="cursor: pointer;">
<a class="link_em" href="?l=1" id="1">List1</a>
</li>
</ul>
You can do it differently by changing the href of the window to the href stored in your a tag, getted by using this instruction:
$(this).children('a').attr("href");
Try to implement, or run the following code snippet to confirm if its resolving your problem & get the expected render:
$('li.tags').on('click', function (e) {
window.location.href= $(this).children('a').attr("href");
return false;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li class="tags" style="cursor: pointer;">
<a class="link_em" href="?l=1" id="1">List1</a>
</li>
Edit
After rereading the question, I think that OP has over complicated things (as have I obviously). Added a simpler version of Demo 1 labeled Demo 2. And added Demo 3 which is solution that uses no JavaScript/jQuery just CSS.
Capture Phase
When you need fine grained control over the event chain .addEventListener() is better suited to handle particular aspects like firing the event on the capture phase instead of the bubbling phase. The reason why is because li.tag will be before a.link on the first phase (i.e. capture phase) of the event chain. We assign capture or bubbling phase by assigning the appropriate boolean value to the third parameter:
document.addEventListener('click', function(e) {...}, false);
This is default, which is set at false for the bubbling phase.
document.addEventListener('click', function(e) {...}, true);
This will make the registered object (e.g.document in this example) listen and act on the capture phase.
The following demo -- during capture phase -- will:
assign the clicked li.tag as the event.target...
...then invoke e.stopPropagation() so the a.link nested within the li.tag will not be included in the event chain. The bubbling phase is skipped as well.
Instead, this a.link becomes e.target on a new event chain because the trigger method .click() is invoked on it.
Test
The 3rd link of List0 tests whether a.link is indeed triggered by .click() or firing during capture or bubbling phase, or is the event.target. Because each a.link in List0 has pointer-events:none which makes a.link oblivious to any mouse or click event from a user, we can conclude:
that any activation of said a.link is entirely dependant upon its parent li.tag being clicked by a user.
during the capture phase, no click event will reach a.link due to e.stopPropagation() and pointer-events:none it can't be e.target.
bubbling phase isn't even there for a.link because of e.stopPropagation()
As that event chain dies off .click() is fired and a.link goes off like a second stage rocket 🚀
When clicking anywhere on the 3rd list item we get these results:
log: Target: LI
Does not jump to List2
When clicking the first or second list item of List0 we get these results:
log: LinkID: {0-1 or 0-2}
log: Target: A
log: Target: LI
Jumps to List1 or List2
Errors
In the Fiddle, there were 2 instances of invalid HTML:
Error 1: <ui>
Correct: <ul>
Error 2: All <a> had the same id, all id must be unique
<a class="link_em" href="?l=1" id="1">List1</a>
<a class="link_em" href="?l=2" id="1">List2</a>
Correct:
<a href='#l1-1' class='link' id='0-1'></a>
<a href='#l1-2' class='link' id='0-2'></a>
You'll notice that the classes and ids in HTML are slightly different, but it'll be ok to change back to your own as long as you mind the errors mentioned above.
Demo 1
document.addEventListener('click', function(e) {
if (e.target.className === 'tag') {
e.stopPropagation();
var link = e.target.querySelector('a');
console.log('ID: ' + link.id);
link.click();
console.log('Target: ' + e.target.tagName);
return false;
} else {
console.log('Target: ' + e.target.tagName);
return false;
}
}, true);
ul {
height: 90px;
outline: 1px solid red;
}
li {
height: 30px;
outline: 1px solid blue;
cursor: pointer
}
.link {
display: block;
width: 10px;
height: 10px;
background: red;
pointer-events: none;
cursor: default;
position: relative;
z-index: 1;
}
b {
float: left;
}
hr {
margin-bottom: 1500px
}
.as-console {
position: fixed;
bottom: 30px;
right: 0;
max-width: 150px;
color: blue;
}
.top {
width: 100px;
pointer-events: auto;
}
<h1>Capture Phase Demo</h1>
<details>
<summary>Test</summary>
<p>Third list item is not a .tag.</p>
<dl>
<dt>Results:</dt>
<dd>Logged: "Target: LI"(or "Target: "B")</dd>
<dd>Did not log: "ID:0-2"</dd>
<dd>Did not jump to List2</dd>
<dt>Conclusion:</dt>
<dd>Links on List0 do not respond to mouse or click events from user. Therefore in order to jump from List0 the link must be triggered programmatically.</dd>
</dl>
</details>
<h2 id='l1-0'>List0</h2>
<ul>
<li class='tag'>
<a href='#l1-1' class='link' id='0-1'>List1</a>
</li>
<li class='tag'>
<a href='#l1-2' class='link' id='0-2'>List2</a>
</li>
<li>
<a href='#l1-2' class='link' id='0-2'>Test</a>
</li>
</ul>
<b>1500px to List1 🔻</b>
<hr>
<h2 id='l1-1'>List1</h2>
<ul>
<li><a class='top' href='#l1-0'>Back to Top</a></li>
<li>Item</li>
</ul>
<b>1500px to List2 🔻</b>
<hr>
<h2 id='l1-2'>List2</h2>
<ul>
<li>
<a class='top' href='#l1-0'>Back to Top</a>
</li>
<li>Item</li>
</ul>
Demo 2
document.addEventListener('click', function(e) {
if (e.target.className === 'tag') {
e.stopPropagation();
e.target.querySelector('a').click();
}
}, true);
ul {
height: 100px;
outline: 1px solid red
}
li {
height: 100%;
cursor:pointer;
}
<h1 id='list0'>List0</h1>
<ul>
<li class='tag'>
<a href='#list1'>List1</a>
</li>
</ul>
<hr>
<h1 id='list1'>List1</h1>
<ul>
<li class='tag'>
<a href='#list0'>List0</a>
</li>
Demo 3
ul {
height: 100px;
outline: 1px solid blue
}
li {
height: 100%;
padding: 0
}
a {
display: block;
height: 100%;
width: 100%;
}
<h1 id='list0'>List0
<h1>
<ul>
<li>
<a href='#list1'>List1</a>
</li>
</ul>
<hr>
<h1 id='list1'>List1
<h1>
<ul>
<li>
<a href='#list0'>List0</a>
</li>
</ul>
Related
I need to apply the events delegation approach in my script.js. So, I need to catch all 'click' events on the body tag. It cannot be changed. I use:
document.body.addEventListener('click', function(event) {
... // here I catch clicks on different elements
}, true);
I have a parent block and unknown amount of internal nested blocks, children / grandchildren / grand-grand..., etc.
I want to click anywhere on parent area, on any of child, grandchild area, and then I want to catch an event with 'parent' class name in event.target.classList array.
Please, see example code https://codepen.io/appalse/pen/xxbqWLr
document.body.addEventListener('click', function(event) {
alert(event.target.classList);
if (event.target.classList.contains('parent')) {
/* I want to catch certain target with 'parent' class name */
event.target.classList.toggle('yellow-background');
}
}, true);
div {
border: 1px solid black;
margin: 10px;
padding: 10px;
}
div.parent {
border-width: 3px;
}
.container {
width: 300px;
background-color: lightgrey;
}
.parent {
background-color: lightblue;
cursor: pointer;
}
.child {
background-color: lightgreen;
}
.grandchild {
background-color: cyan;
}
.yellow-background {
background-color: yellow;
}
<div class="container"> Container
<div class="parent"> Parent - it catches click. OK
<div class="child">Child - doesn't catch click.
<div class="grandchild">Grandchild - doesn't catch click</div>
<div class="grandchild">Grandchild - doesn't</div>
</div>
</div>
</div>
I have read about events bubbling and capturing. I used a 'useCapture = true' value in addEventListener in order to catch an event during capturing phase. But it works another way. I thought that addEventListener method is called for every tag which is under clicked area, but I see that the addEventListener method is called for only the last child event.
I cannot use event.target.parentNode because I don't know how deeply this targeted child is. However, I can recursively call for parentNode until I get necessary parent. But this solution looks strange for me, like a hack. Am I wrong? Is this recursive approach is common and good?
The question seems really basic, but in the articles "capturing and bubbling", "event delegation" I see only the simplest examples without an unknown amount of nested elements. Or they propose the solution with the addEventListener method on parent tag, which I cannot use (because I use document.body.addEventListener).
Please, help me to find a solution and to get a better understanding of this issue. Any link or google keywords would be appreciated.
Use event.target.closest('.parent') to see if the clicked element has an ancestor which is a .parent. If so, change the parent's class, otherwise do nothing:
document.body.addEventListener('click', function(event) {
const parent = event.target.closest('.parent');
if (parent) {
parent.classList.toggle('yellow-background');
}
}, true);
div {
border: 1px solid black;
margin: 10px;
padding: 10px;
}
div.parent {
border-width: 3px;
}
.container {
width: 300px;
background-color: lightgrey;
}
.parent {
background-color: lightblue;
cursor: pointer;
}
.child {
background-color: lightgreen;
}
.grandchild {
background-color: cyan;
}
.yellow-background {
background-color: yellow;
}
<h1>How to catch an event on Parent element (event.target.classList should contain 'parent') if I click on any of Grandchild / Child / Parent?</h1>
<div class="container"> Container
<div class="parent"> Parent - it catches click. OK
<div class="child">Child - doesn't catch click.
<div class="grandchild">Grandchild - doesn't catch click</div>
<div class="grandchild">Grandchild</div>
</div>
<div class="child">
<div class="grandchild">Grandchild</div>
<div class="grandchild">Grandchild</div>
</div>
</div>
</div>
<div class="container">
<div class="parent"> Parent - should catch click
<div class="child">
<div class="grandchild">Grandchild</div>
<div class="grandchild">Grandchild</div>
</div>
<div class="child">
<div class="grandchild">Grandchild</div>
<div class="grandchild">Grandchild</div>
</div>
</div>
</div>
Bubbling vs capturing doesn't make much of a difference here - at least in this situation, you can attach the listener in the bubbling phase too, and it'll work just as well.
I thought that addEventListener method is called for every tag which is under clicked area, but I see that the addEventListener method is called for only the last child event.
Every ancestor element of a clicked element will have a click event listener run, if it has one attached to the element - but here, you only have a single click listener, attached to the document.body, so only one callback runs when there's a click.
Just add listener to document.body, and let event bubbling solves your problem.
There are 3 buttons in my code.
One is to add more files. (.btn-plus)
One is to remove the one added. (.btn-minus)
One is to reset the file. (.btn-reset)
I could add more input with (.btn-plus) button.
How could I delete only the one I click among every input I add with (.btn-plus)?
$(".btn-plus").click(function(){
$('.board-box__attachments').prepend('<li><div class="th">files</div><div class="td"><input type="file"><button class="btn btn-minus"> - </button></div></li>');
return false;
})
$(".btn-minus").click(function(){
$(this).nextUntil('li').remove()
})
$(".btn-reset").click(function(){
$(".board-box__attachments input").value = "";
})
li {
width : 60%;
background : lightblue;
list-style : none;
padding : 0.5em;
border-bottom : 1px solid white;
}
.th {
width : 100px;
float: left;
}
.td {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="board-box__attachments">
<li>
<div class="th">files</div>
<div class="td">
<input type="file">
<button class="btn btn-plus"> + </button>
<button class="btn-reset">Reset</button>
</div>
</li>
</ul>
You have to use on() to attach event to dynamically added element. Then use closest() to find currently clicked element's parent.
$("body").on("click", ".btn-minus", function(){
$(this).closest('li').remove();
})
$(this).nextUntil("li") doesn't match anything. It only searches siblings of this, and the button doesn't have any li siblings. If you want to select the li containing the button, use $(this).closest("li").
You also need to use event delegation to bind an event handler to dynamically-created elements.
$(".btn-plus").click(function(){
$('.board-box__attachments').prepend('<li><div class="th">files</div><div class="td"><input type="file"><button class="btn btn-minus"> - </button></div></li>');
return false;
})
$(".board-box__attachments").on("click", ".btn-minus", function(){
$(this).closest("li").remove()
})
li {
width : 50%;
background : lightblue;
list-style : none;
padding : 1em;
border-bottom : 1px solid white;
}
.th {
width : 100px;
float: left;
}
.td {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="board-box__attachments">
<li>
<div class="th">files</div>
<div class="td">
<input type="file">
<button class="btn btn-plus"> + </button>
</div>
</li>
</ul>
There are 2 issues with you code:
Your element which contains btn-minus is being created dynamically. So the click event would not work instead you need to use on event.
$(".btn-minus").click(function(){
So instead of this you need to use
$(document).on('click', '.btn-minus', function() {
Also you need to use following code to remove element.
$(this).closest('li').remove();
Please see the updated JSFiddle
Here you can creating elements dynamically so once the page is loaded, browser has no knowledge of '.btn-minus'
Try this:
$(document).on('click', '.btn-minus', function(){
$(this).closest('li').remove()
})
Hope this helps!
I have tried to follow this solution to create a resizable side navigation bar: How can I resize a DIV by dragging just ONE side of it?.
I am able to create a resizable div, however, the div also resizes everytime I click to select a menu option. I only want the Sidenav to resize when dragged by a mouse.
One thing to note: Due to the tool I use to develop the site, I cannot use #id, so I have to use a class instead.
Here is the code:
var i = 0;
var dragging = false;
$('.sidenav-wrapper').mousedown(function(e){
e.preventDefault();
dragging = true;
var main = $('.body')
var ghostbar = $('<div>',
{id:'ghostbar',
css: {
height: main.outerHeight(),
top: main.offset().top,
left: main.offset().left
}
}).appendTo('body');
$(document).mousemove(function(e){
ghostbar.css("left", e.pageX + 2);
});
});
$(document).mouseup(function(e){
if (dragging)
{
$('.sidenav-wrapper').css("width", e.pageX + 2);
$('.main').css("left", e.pageX + 2);
$('#ghostbar').remove();
$(document).unbind('mousemove');
dragging = false;
}
});
.sidenav-wrapper
{
flex: 0 1 auto;
resize: horizontal;
cursor: e-resize;
overflow: auto;
width: 300px;
margin-left: 8.5%;
margin-top: 0px;
display: block;
background-color: #f7f7f7;
}
#ghostbar
{
width:3px;
background-color:#000;
opacity:0.5;
position:absolute;
cursor: col-resize;
z-index:999
}
<div class="sidenav-wrapper>
<div class=" sidenav-container ">
<ul class="menu">
<li>
<a>Menu </a>
</li>
<li>
<a>Menu </a>
</li>
<li>
<a>Menu </a>
</li>
<li>
<a>Menu </a>
</li>
<li>
<a>Menu </a>
</li>
<li>
<a>Menu </a>
</li>
<li>
<a>Menu </a>
</li>
</ul>
</div>
</div>
<div class="body">
<p> Some content here</p>
</div>
Thanks for the help!
In the example you linked, there is a dragbar element. You need to add that element to your HTML and perform the mousedown event on that element.
stop bubbling on the ul to the parent by prevent default. Each time you click, mouse down and mouse up are triggered on the parent element. so, just stop propagating the event at the child.
I mean to say, adding the following lines to the js should stop the sidebar to resize on clicking the ul element.
$("ul").on('click',fucntion (e) {
e.stopPropagation();
});
PS: i tried running your code on Codepen, it doesn't seem to work, can you add a codepen/jsbin live example which actually works.
There is a piece of code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Events Examples</title>
<style>
ul {
width: 200px;
height: 200px;
margin: 10px;
background-color: #ccc;
float: left;
}
.highlight {
background-color: red;
}
</style>
</head>
<body>
<ul id="list">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
<script>
const ul = document.querySelector('#list');
ul.addEventListener('mouseover', highlight);
ul.addEventListener('mouseout', highlight);
function highlight(event) {
console.log(event.target);
event.target.classList.toggle('highlight');
}
</script>
</body>
</html>
When started, it looks like this:
What I expect from listeners. When I move the mouse over the grey zone of 'ul', then highlight() function should work. Well, it works fine. What I don't understand: when I move the mouse over 'li' elements, then highlight() works again for unknown reason. How can it be fixed?
I'm new to JS and I have not found the answer to the problem described.
If you're saying you only want to highlight the entire region, then use "mouseenter" and "mouseleave" instead, and this to reference the element.
Then there's no event bubbling issue to have to deal with.
const ul = document.querySelector('#list');
ul.addEventListener('mouseenter', highlight);
ul.addEventListener('mouseleave', highlight);
function highlight(event) {
this.classList.toggle('highlight');
}
ul {
width: 200px;
height: 200px;
margin: 10px;
background-color: #ccc;
float: left;
}
.highlight {
background-color: red;
}
<ul id="list">
<li>First</li>
<li>Second</li>
<li>Third</li>
</ul>
Events always bubble up, but if you want to select the element that you actually added the listener to, use event.currentTarget instead of event.target.
function highlight(event) {
console.log(event.currentTarget);
event.currentTarget.classList.toggle('highlight');
}
Information on event bubbling: What is event bubbling and capturing?
If you want to be sure that the event only gets called on the element that you registered it on, you can check if target matches currentTarget.
If you mouseover one element contained within another, the "inner" element (in your case the <li> will fire a mouseover event, and this will "bubble" up to the element where you attached the listener. The target property on the event will be the inner element that triggered the event, not the one where you attached the listener.
Rather than target, use currentTarget, which indicates the element that you attached the listener to.
update As #Terminus points out, this could lead to multiple handlings of the event, since mousing over both the li and the ul will trigger mouseover events that get handled by the listener. The solution would then be to only run the code if the target is the currentTarget
if(event.target === event.currentTarget)
event.currentTarget.classList.toggle('highlight');
So my HTML looks like this :
<section id="nav-wrapper">
<div class="container">
<ul id="ul-main_wrapper">
<li class="item_1""></li>
<li class="item_2"></li>
<li class="item_3"></li>
<li class="item_4"></li>
<li class="item_5"></li>
<li class="item_6"></li>
<li class="item_7"></li>
<li class="item_8"></li>
<li class="item_9"></li>
<li class="item_10"></li>
<li class="close_clear">Back</li>
</ul>
</div>
</section>
I have a list that sits all onto one line so the CSS looks like :
#nav-wrapper {
width: 100%!important;
height: auto!important;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
overflow-x: scroll;
bottom: 80px;
}
#ul-main_wrapper li {
height: 20px;
width: 20px;
margin: 2px;
display: inline-block;
}
onClick of the last item in the list that is back i'm trying to get the list to scroll back to the first item. But can't find anything that works.
EDIT :
Here's a JSFiddle : https://jsfiddle.net/svuh0mvj/1/
Just use scrollTop():
$('.close_clear').on('click', function() {
$(window).scrollTop($('.item_1').offset().top);
})
DEMO
Or with animate:
$('.close_clear').on('click', function() {
var body = $("html, body");
body.stop().animate({scrollTop:$('.item_1').offset().top}, '500');
})
DEMO
To scroll to the left within your #nav-header div, use:
$('.close_clear').on('click', function() {
var nav = $("#nav-wrapper");
nav.animate({scrollLeft: $('.item_1').offset().left}, '500');
})
DEMO
If you are not using jquery, or don't want to use it, you could try to use .scrollIntoView()
MSDN: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
"The Element.scrollIntoView() method scrolls the current element into the visible area of the browser window."
though this is not implemented the same way in all the different browsers we got these days.
document.getElementsByClassName('item_1')[0].scrollIntoView();
Or you could assign an id to the first item, or pass this id: 'ul-main_wrapper' and pass it to this example function
function scrollToAnchor(anchor) {
var scrollLocation = document.location.toString().split('#')[0];
document.location = scrollLocation + '#' + anchor;
}
which will do the scrolling for you.
If you do use jquery, use the solution by mmm