This question already has an answer here:
Preventing event bubbling
(1 answer)
Closed 1 year ago.
I'm trying to create a custom dropdown field. Following is my code:
const list = document.querySelector('.list');
const listItems = list.getElementsByTagName('p');
document.querySelector('#category').onclick = function() {
// Able to show the .list DOM element
list.style.display = 'block';
}
Array.from(listItems)
.forEach(function(listItem) {
listItem.onclick = function() {
// Unable to hide the .list DOM element
list.style.display = 'none';
// .listItem's value is getting logged
console.log(listItem.getAttribute('value'));
}
}
);
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<div class="list">
<p value="">None</p>
<p value="1">harum inventore</p>
<p value="2">dolorem voluptatem</p>
<p value="3">dolores consectetur</p>
<p value="4">velit culpa</p>
<p value="5">beatae nulla</p>
</div>
</div>
I'm not exactly sure where it is going wrong as I'm able to set the list's style attribute to to display: block but I'm unable to set it back to display: none even though I'm trying to do the same thing from two different places.
I'm pretty new to Javascript. This might be a duplicate but I honestly tried looking up and trying as many solutions as possible. But none seemed to work. So apologies in advance.
Your code was working right, but because your parent and child had click event listeners on them, you were hiding and showing the list back.
Here you can find more about event bubbling: https://javascript.info/bubbling-and-capturing
const list = document.querySelector('.list');
const listItems = list.getElementsByTagName('p');
// Overlapping event listener 1
document.querySelector('#category').onclick = function() {
list.style.display = 'block';
}
Array.from(listItems)
.forEach(function(listItem) {
// Overlapping event listener 2
listItem.onclick = function(event) {
// to fix double event issue, you have to prevent its bubbling up
event.stopImmediatePropagation();
list.style.display = 'none';
// Uncomment the debugger here and you will see that this code works right
// debugger;
console.log(listItem.getAttribute('data-value'));
}
}
);
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<div class="list">
<p data-value="">None</p>
<p data-value="1">harum inventore</p>
<p data-value="2">dolorem voluptatem</p>
<p data-value="3">dolores consectetur</p>
<p data-value="4">velit culpa</p>
<p data-value="5">beatae nulla</p>
</div>
</div>
The event listener of your parent #category element is executed every time you click one of it's child elements. You can prevent the click event from bubbling up to the parent element by using the stopPropagation() method on the child elements' listener.
Also, as #connexo mentioned in the comments, <p> elements do not have a value attribute. You are probably looking for a <ul> element with <li> children.
Check and run the following Code Snippet for a practical example of the above approach:
const list = document.querySelector('.list');
const listItems = document.querySelectorAll('.list li');
document.querySelector('#category').onclick = function() {
list.style.display = 'block'; // Able to show the .list DOM element
}
Array.from(listItems)
.forEach(function(listItem) {
listItem.onclick = function(e) {
e.stopPropagation(); // prevent parent event listener from being executed
list.style.display = 'none'; // Unable to hide the .list DOM element
}
}
);
.list {display: none; list-style: none;}
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<ul class="list">
<li>None</li>
<li>harum inventore</li>
<li>dolorem voluptatem</li>
<li>dolores consectetur</li>
<li>velit culpa</li>
<li>beatae nulla</li>
</ul>
</div>
Related
I have been trying to add an event listener to three li elements. The issue is that doing document.getElementbyid(about) doesn't work. But when I do get element document.getElementbyid(last), it works but on all three elements simultaneously.
I want to target them individually so that it opens up models respective to each li element.
Here is the HTML:
<section>
<h1 id="intro">Hi, My name is</h1>
<div id="last">
<ul>
<li id="about">About</li>
<li id="project">Projects</li>
<li id="resume">Resume</li>
</ul>
</div>
</section>
well like this :
let lis = document.querySelectorAll('section div ul li')
lis.forEach((li) => {
li.addEventListener('click', () => {
})
})
if you want you can handle each li individually like so :
let resume = document.querySelector('#resume')
resume.addEventListener('click', () => {
// do whatever you need
})
the click event here is an exemple, use the event you need, like perhaps mouseover
It is possible to add a click event to each element. You would use querySelectorAll to reference them all. You would then loop over it.
document.querySelectorAll("#last li").forEach(function (li) {
li.addEventListener("click", function () {
console.log("Clicked", li.id);
});
});
A better option is event delegation where you just bind one event.
Here is the basic idea using event delegation. Add one click event listener on a parent. You select the target which is what was clicked and find the link. You can use the link's hash to get the element you want to show.
// Bind one click event to the parent, we will use event delegation
document.getElementById("last").addEventListener("click", function(event) {
// get what was clicked and look for the anchor tag
const anchor = event.target.closest("a");
// did we find an anchor?
if (anchor) {
// hash will have the id of the element to show
showElement(anchor.hash);
}
});
function showElement(selector) {
// do we have an active modal? Yes? Close it
const modalActive = document.querySelector(".modal.active");
if (modalActive) {
modalActive.classList.remove('active');
}
// Enable the modal we just clicked on
const elem = document.querySelector(selector);
if (elem) {
elem.classList.add('active');
}
}
// Page load, if has hash then show that element
const hash = window.location.hash;
if (hash) {
showElement(hash);
}
.modal {
display: none;
}
.active {
display: block;
}
<section>
<h1 id="intro">Hi, My name is</h1>
<div id="last">
<ul>
<li>About</li>
<li>Projects</li>
<li>Resume</li>
</ul>
</div>
</section>
<div id="about" class="modal">ABOUT CONTENT</div>
<div id="project" class="modal">PROJECT CONTENT</div>
<div id="resume" class="modal">RESUME CONTENT</div>
Sorry for the lack of knowledge but I don't know where else to turn. I had been working on the CSS for a project while the javascript was handled by a colleague. That colleague has now left the company and I have to finish his work to hit a deadline with very little knowledge of javascript. He had created a simple function (show/hide) that allowed us to show and hide content with an unordered list. Namely when you click on a list item, the corresponding div shows and the rest hides.
This was working fine, however I have since been asked to duplicate this so that multiple (show/hides) can be used on the page. When I did this the first one works ok, but the next scripts intefere with eachother and also hide content in the other divs. I've tried to fix this using my non-existent knowledge of javascript but to know avail (attempt is below). Any help here would be massively appreciated. Thanks in advance!
function toggle(target) {
var artz = document.getElementsByClassName('history');
var targ = document.getElementById(target);
var isVis = targ.style.display == 'block';
// hide all
for (var i = 0; i < artz.length; i++) {
artz[i].style.display = 'none';
}
// toggle current
targ.style.display = isVis? 'none' : 'block';
return false;
}
function toggle2(target) {
var artz2 = document.getElementsByClassName('vision');
var targ2 = document.getElementById(target2);
var isVis2 = targ.style.display == 'block';
// hide all
for (var i = 0; i < artz2.length; i++) {
artz2[i].style.display = 'none';
}
// toggle current
targ2.style.display = isVis2? 'none' : 'block';
return false;
}
jQuery(document).ready(function($) {
$('.slide-menu li a').on('click', function(){
$(this).parent().addClass('current').siblings().removeClass('current');
});
});
.container {
float: left;
}
.display-item {
display: none;
}
.display-item:first-of-type {
display: block;
}
.slide-menu li.current a {
color: #75aaaf;
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<div class="container">
<ul class="slide-menu" id="first">
<li class="current">1348</li>
<li>1558</li>
<li>1590</li>
</ul>
<div class="display-item history" id="1348" style="display:block;">History Content</div>
<div class="display-item history" id="1558">History Content2</div>
<div class="display-item history" id="1590">History Content3</div>
</div>
<div class="container">
<ul class="slide-menu" id="second">
<li class="current">Introduction</li>
<li>Highways</li>
<li>Transport</li>
</ul>
<div class="display-item vision" id="base" style="display:block;">Vision Content</div>
<div class="display-item vision" id="highways">Vision Content2</div>
<div class="display-item vision" id="transport">Vision Content3</div>
</div>
I think your code is okay if you intend duplicating the first toggle function in toggle2 function all you have to do is
Change the onclick event function from toggle to toggle2
<div class="container">
<ul class="slide-menu" id="second">
<li class="current"><a href="#/"
onclickk="toggle2('base');">Introduction</a></li>
<li><a href="#/"
onclick="toggle2('highways');">Highways</a></li>
<li><a href="#/"
onclick="toggle2('transport');">Transport</a></li>
</ul>
<div class="display-item vision" id="base"
style="display:block;">Vision Content</div>
<div class="display-item vision" id="highways">Vision
Content2</div>
<div class="display-item vision" id="transport">Vision
Content3</div>
</div>
This really isn't the way to set this up as it just causes the code to grow as more items need to be shown/hidden and the new code is largely the same as the old code. The original code also is more complex than it need be.
The following code will work no matter how many container structures you put on the page as long as you keep the structure the same as it is now. No ids are needed. No JQuery is needed either. You'll never need to touch the JavaScript, just add/remove HTML containers as you see fit.
See comments inline for details on what's happening.
.container {
float: left;
border:1px solid #e0e0e0;
margin:10px;
width:25%;
padding:3px;
}
/* Don't use hyperlinks <a></a> when you aren't
navigating anywhere. If you just need something
to click on, any element will do.
We'll just style the clickable elements to look like links
*/
.slide-menu > li {
text-decoration:underline;
cursor:pointer;
color: #75aaaf;
}
.hidden { display: none; } /* This class will be toggled upon clicks */
<!--
Don't use hyperlinks <a></a> when you aren't
navigating anywhere. If you just need something
to click on, any element will do.
The elements that should be hidden by default
will be so because of the "hidden" class that
they start off with.
No JQuery needed for this. Keep the HTML clean and
do all the event binding in JavaScript (no onclick="...")
-->
<div class="container">
<ul class="slide-menu">
<li class="current">1348</li>
<li>1558</li>
<li>1590</li>
</ul>
<div class="history" id="1348">History Content</div>
<div class="history hidden" id="1558">History Content2</div>
<div class="history hidden" id="1590">History Content3</div>
</div>
<div class="container">
<ul class="slide-menu">
<li class="current">Introduction</li>
<li>Highways</li>
<li>Transport</li>
</ul>
<div class="vision" id="base">Vision Content</div>
<div class="vision hidden" id="highways">Vision Content2</div>
<div class="vision hidden" id="transport">Vision Content3</div>
</div>
<!-- The following function will run automatically when this script element
is reached. Always keep the script just before the closing body tag (</body>). -->
<script>
(function(){
// Get any/all slide-menu elements into an array
let menus =Array.prototype.slice.call(document.querySelectorAll(".slide-menu"));
// Loop over the menus
menus.forEach(function(menu){
// Loop over the list items in the menu
Array.prototype.slice.call(menu.querySelectorAll("li")).forEach(function(item, index){
let idx = index;
// Set up a click event handler for each item
item.addEventListener("click", function(){
// Get all the <div> items in this menu into an Array
let divs = Array.prototype.slice.call(menu.parentElement.querySelectorAll("div"));
// Hide any item that was previously showing
divs.forEach(function(div){ div.classList.add("hidden"); });
// Query the parent element (the container) for all the
// corresponding <div> items and make it visible
divs[idx].classList.remove("hidden");
});
});
});
}());
</script>
I have some code that seems to be working, but in a rather odd fashion. When I first refresh the page I have the close button that seems to work fine, but when I make a new to-do list item the close button seems to cease working and I can't pinpoint why.
let addItem = document.getElementById("submitButton");
let userInput = document.getElementById("toDoInput");
let output = document.getElementById("output");
let toDoItem = document.querySelector(".toDoItem");
let close = document.querySelector(".close");
let toDo = document.querySelector(".todo");
/*User clicked the addItem Button
If there is any text inside the text field then add that text to the todo list */
addItem.addEventListener("click", addToDo);
function addToDo(){
var html = `
<ul class="todo">
<li class="toDoItem">
<p>${userInput.value}</p>
<div class="close">X</div>
</li>
</ul>
`;
output.innerHTML += html;
// Resetting input to blank once a submit button has been added.
userInput.value = '';
}
// Figure out how to make closing functionality simple this implementation
// isn't working
close.addEventListener("click", function(e){
console.log("clicked");
let x = e.target.parentElement;
x.style.display = "none";
e.preventDefault();
});
<header>
<input type="text" placeholder="Enter Item Here..." id="toDoInput">
<button id="submitButton">+</button>
</header>
<section id="output">
<ul class="todo">
<li class="toDoItem">
<p>Clean Room!</p>
<div class="close">X</div>
</li>
</ul>
</section>
<script src="todo.js"></script>
I'm also not sure if I'm using best practice as I'm new to web development, so any tips would be thoroughly appreciated as well!
You need a live event handler on your close button(s). This example should help. To offer something more, it's easier and more straight forward to use jQuery for it if you can and don't mind using a JS library.
jQuery example:
$(document).on("click", ".close", function() {
$(this).parent().hide();
});
No need to prevent default behavior since it's a div.
The issue here is that when you re-render the content of the "output" section you lose the event listener bound to the original ".close" element. A few options to work around the issue, have a look at this thread for some examples.
You got pretty close man, and you definitely do not need jQuery.
As you can see below, you don't need to push the <ul> dynamically. It will never change!
<header>
<input type="text" placeholder="Enter Item Here..." id="toDoInput">
<button id="submitButton">+</button>
</header>
<section id="output">
<ul class="todo">
</ul>
</section>
And here is your refactored javascript:
let addItem = document.getElementById("submitButton");
let userInput = document.getElementById("toDoInput");
let output = document.getElementById("output");
let toDoItem = document.querySelector(".toDoItem");
let toDo = document.querySelector(".todo");
/*User clicked the addItem Button
If there is any text inside the text field then add that text to the todo
list */
addItem.addEventListener("click", addToDo);
function addToDo(e){
e.preventDefault();
var html = `<li class="toDoItem">
<p>${userInput.value} </p> <p class="close"
onclick="removeChildElement(this);">X</p>
</li>`;
output.innerHTML += html;
let close = document.querySelector(".close")
// Resetting input to blank once a submit button has been added.
userInput.value = '';
}
// Figure out how to make closing functionality simple this implementation
// isn't working
function removeChildElement(e) {
let x = e.parentElement;
let xParent = x.parentElement;
xParent.removeChild(x);
console.log(xParent);
}
As you can see i made a few changes. Most importantly your close button issue. The function gets the parent on its parent ( ^ 2 ) and then removes its child. Which would be your <li> element!
Enjoy the fiddle: https://jsfiddle.net/fjbyy6uw/35/
Use Event Delegation. Details are commented in Demo. Added a <form> so HTMLFormControlsCollection API can be used, it's simpler, less writing, and I'm lazy.
/* All form controls are referenced by HTMLFormControlsCollection */
var form = document.forms.toDo;
var td = form.elements;
var add = td.add;
var inp = td.input;
var out = td.output;
var toDo = document.querySelector('.toDo');
add.addEventListener("click", addToDo);
/* Limited the dynamically created node to `<li>`. It doesn't make sense to
|| have several `<ul>` having only one `<li>` each.
*/
function addToDo() {
var html = `
<li class="item">
<span>${inp.value}</span>
<b class="close">X</b>
</li>
`;
toDo.innerHTML += html;
}
/* Event Delegation is a way of leveraging event bubbling so
|| that a single ancestor node can be registered to listen for
|| an event (e.currentTarget) and by means event propagation
|| (bubbling) can locate the event origin (node clicked/e.target).
|| In this demo e.currentTarget is output#output and e.target are
|| any b.close. This was possibble by using e.target in conditions
*/
/* removeChild() is used because display:none is not entirely
|| gone. The markup remains just not in the DOM, so it may not
|| look like it's there, under certain conditions a node could be
|| considered present.
*/
out.addEventListener("click", function(e) {
if (e.target !== e.currentTarget) {
if (e.target.className === "close") {
let x = e.target.parentElement
x.parentElement.removeChild(x);
}
}
});
.item {
display: flex;
max-width: 250px;
justify-content: space-between;
}
.item span,
.item b {
display: table-cell;
}
.item b {
cursor: pointer
}
input,
output,
button {
font: inherit
}
<form id='toDo'>
<header>
<input type="text" placeholder="Enter Item Here..." id="input">
<button id="add" type='button'>+</button>
</header>
<output id="output">
<ul class="toDo">
<li class="item">
<span>Clean Room!</span>
<b class="close">X</b>
</li>
</ul>
</output>
</form>
My HTML looks like this:
<div class="panel">
<div class="panel-heading"></div>
<div class="panel-body"></div>
</div>
I am currently selecting the parent node of the panel-heading element like so:
e.target.parentNode
This leaves me with the panel class. All is well.
But now I would like to grab the panel-body at that point. Doing something like this unfortunately does not work:
e.target.parentNode.querySelector('.panel-body')
Is there a clean way to do this in vanilla javascript?
If you know the node's class, you can always use document object:
var tgt = document.querySelector('.panel-body');
If you need to get nodes in the context of an event such as click, you can delegate.
Find node that is an ancestor of all of the nodes you wish to access.
ex. .panel
Register the event on that node.
ex. panel.addEventListener('click', callback)
During the bubbling phase, find the event.target by comparing it to the event.currentTarget (the node that is registered to the event)
ex. if(e.target !== e.currentTarget) {...
Click nodes and it's tag and class will be displayed.
Details are commented in snippet
Snippet
// Reference top element
var panel = document.querySelector('.panel');
// Register .panel on click event
panel.addEventListener('click', highlight);
function highlight(e) {
// if the clicked node is not .panel
if (e.target !== e.currentTarget) {
// Get the clicked node's class
var tgtClass = e.target.className;
// Get the clicked node's tag
var tgtTag = e.target.tagName;
}
/* Set the clicked node's tag and class
|| as it's content.
*/
e.target.textContent += ' ' + tgtTag + '.' + tgtClass;
}
[class*=panel] {
border: 1px dashed blue;
color: red;
}
<section class="panel">
<hgroup class='panel-heading-group'>
<h1 class="panel-heading">HEADING</h1>
<h2 class='panel-sub-heading'>SUB-HEADING</h2>
</hgroup>
<main class="panel-body">
<p>CONTENT A</p>
<p>CONTENT B</p>
</main>
<footer class='panel-footer'>FOOTER</footer>
</section>
I am trying to style div and ul to function like . However, I have a problem that:
1) I only want to toggle the ul that I click and hide the other ul. So I wonder if jquery support some function such as 'not click'?
2) I want to hide all the ul when the mouse is click outside. I did some research, and see other people use mouseup or click on body. But I am not quiet sure how it works.
$(document).ready(function() {
$('.hide').each(function() {
$(this).hide();
});
$('.select').click(function() {
var id = '#' + $(this).attr('id');
var sub = id + '_sub';
$(sub).slideToggle();
});
$('body').mouseup(function() {
if($(this).length == 0) {
$(this).hide();
}
});
});
div.select {
display: inline-block;
margin: 10px;
padding: 20px;
background: red;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="1" class="select">
<div class="main">
<span>1</span>
</div>
<div>
<ul id="1_sub" class="hide">
<li>1</li>
<li>2</li>
</ul>
</div>
</div>
<div id="2" class="select">
<div class="main">
<span>1</span>
</div>
<div>
<ul id="2_sub" class="hide">
<li>1</li>
<li>2</li>
</ul>
</div>
</div>
<div id="3" class="select">
<div class="main">
<span>1</span>
</div>
<div>
<ul id="3_sub" class="hide">
<li>1</li>
<li>2</li>
</ul>
</div>
</div>
</body>
here you go: DEMO
$(document).ready(function() {
$('.hide').hide(); //hide in the beginning
$('.select').click(function() {
$('.hide').slideUp(200); //hide all the divs
$(this).find('.hide').slideDown(200); //show the one that is clicked
});
$(document).click(function(e){
if(!$('.select').is(e.target) || !$('.select').has(e.target)){ // check if the click is inside a div or outside
$('.hide').slideUp(200); // if it is outside then hide all of them
}
});
});
you can define your notClick() function as below:
$.fn.notClicked= function(clickPosition){
if (!$(this).is(clickPosition.target) && $(this).has(clickPosition.target).length === 0){
return true;
}
else{
return false;
}
};
and then use it as:
$(document).click(function(e){
alert($('.select').notClick(e)); // will return true if it is not clicked, and false if clicked
});
You need to hide other ul whenever some one clicks on .select div.
Here is a working fiddle: http://jsfiddle.net/0mgbsa0b/1/
$(document).ready(function() {
$('.hide').each(function() {
$(this).hide();
});
$('.select').click(function() {
$('.hide').each(function() {
$(this).hide();
});
var id = '#' + $(this).attr('id');
var sub = id + '_sub';
$(sub).slideToggle();
});
$('body').mouseup(function() {
if($(this).length == 0) {
$(this).hide();
}
});
});
I'm interested in two concerns you raised, so i will be trying to share some ideas on them:
1)So I wonder if jquery support some function such as 'not click'?
personally, to quesiton1
i think there is no jQuery event method called .noclick()
PPL often use addClass & removeClass to log whether an element got clicked and after marking the element with class="active" , using jQuery selector to select ".active" or using jQuery ":not" selector to select elements that are not marked ".active" ( indirectly finding out those unclicked.)
3.You might also need to count in click propagation issues. meaning sometimes you click a children container and triggered click event towards all its parent inside.
fiddle link: `http://jsfiddle.net/hahatey/ctp5jngf/2/`
In the above case , if you clicked child box in red, will by default alert1, alert2 if
you didn't apply a e.stopPropagation() to the click event;
2) I want to hide all the ul when the mouse is click outside. I did some research, and see other people use mouseup or click on body. But I am not quiet sure how it works.
for question 2:
could be many many ways to do it, you can try blur() //lose focus event trigger.
like what you mentioned mouseout, mouseup, add click event listener to outer area all will work for it as long as u can use method in answer1. i see other ppl have posted many answers already as it can be done in many ways.