Multiple modals won't close if number of modal is unknown - javascript

I am trying to get the below code to work but it only seems to work for the last modal in the list.
var modals = document.getElementsByClassName('modal');
var btns = document.getElementsByClassName("pop-up");
var spans=document.getElementsByClassName("close-modal");
for(let i=0;i<btns.length;i++){
btns[i].onclick = function() {
modals[i].style.display = "block";
}
}
for(let i=0;i<spans.length;i++){
spans[i].onclick = function() {
modals[i].style.display = "none";
}
}
}
// When the user clicks anywhere outside of the modal, close it
for(let i=0;i<modals.length;i++){
window.onclick = function(event) {
if (event.target == modals[i]) {
modals[i].style.display = "none";
}
}
}
}
Can someone help clear up where I am going wrong?
EDIT: Below is a snippet using the event listening described in an answer below - this snippet shows that implementing this event listener prevents the button from working on click - am I implementing it incorrectly? If I remove the code in JS to do with clicking anywhere then the rest works.
function submitBtn(){
// Get the modal
var modals = document.getElementsByClassName('modal');
// Get the button that opens the modal
var btns = document.getElementsByClassName("pop-up");
var spans=document.getElementsByClassName("close-modal");
for(let i=0;i<btns.length;i++){
btns[i].onclick = function(){
modals[i].style.display = "block";
}
}
for(let i=0;i<spans.length;i++){
spans[i].onclick = function(){
modals[i].style.display = "none";
}
}
// When the user clicks anywhere outside of the modal, close it
window.addEventListener('click', function(){
var modals = document.getElementsByClassName('modal');
for(let i = 0; i < modals.length; i++){
modals[i].style.display = "none";
}
});
}
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 10; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
.modal-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
display: table;
}
/* Modal Content */
.modal-content div {
padding: 5px;
margin: 5px;
}
/* The Close Button */
.close-modal {
color: #aaaaaa;
text-align: right;
font-size: 28px;
font-weight: bold;
}
.close-modal:hover,
.close-modal:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
<div class="modal">
<div class="modal-content">
stuff1<p class="close-modal">×</p>
</div>
</div>
<div class="modal">
<div class="modal-content">stuff2<p class="close-modal">×</p></div>
</div>
<span>
<button type="button" class="pop-up" onclick="submitBtn()">SEND</button></span>
<span>
<button type="button" class="pop-up" onclick="submitBtn()">SEND</button></span>

The below is what you are looking for. As mentioned, you are overwriting the event that you've bound to window by looping through and recreating it, but the event only needs to be registered once, as demonstrated below.
window.addEventListener('click', function() {
var modals = document.getElementsByClassName('modal');
for (let i = 0; i < modals.length; i++) {
modals[i].style.display = "none";
}
});

Your for loop is replacing the window.onclick listener each time, instead use window.addEventListener('click', function....)
You may wish to store references to these functions so you can window.removeEventListener later on.

So is this close to what you're looking for?
I mostly reused your code and deleted what was in the way.
The essential part is that inside the click handler you're using to close the modals you need to check if the target of the click was one of the buttons. I also removed the spans around the buttons which seemed to not be of any meaningful use.
Lastly, I replaced your onclick = by addEventListener('click', which in contrast to onclick allows for more than one event handler for any given event.
var modals = document.getElementsByClassName('modal');
var btns = document.getElementsByClassName("pop-up");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function() {
modals[i].style.display = "block";
})
}
document.body.addEventListener('click', function(e) {
if (e.target.matches('button.pop-up')) return;
for (let i = 0; i < modals.length; i++) {
modals[i].style.display = "none";
}
});
.modal {
display: none;
/* Hidden by default */
position: fixed;
/* Stay in place */
z-index: 10;
/* Sit on top */
padding-top: 100px;
/* Location of the box */
left: 0;
top: 0;
width: 100%;
/* Full width */
height: 100%;
/* Full height */
overflow: auto;
/* Enable scroll if needed */
background-color: rgb(0, 0, 0);
/* Fallback color */
background-color: rgba(0, 0, 0, 0.4);
/* Black w/ opacity */
}
.modal-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
display: table;
}
/* Modal Content */
.modal-content div {
padding: 5px;
margin: 5px;
}
/* The Close Button */
.close-modal {
color: #aaaaaa;
text-align: right;
font-size: 28px;
font-weight: bold;
}
.close-modal:hover,
.close-modal:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
<div class="modal">
<div class="modal-content">
stuff1
<p class="close-modal">×</p>
</div>
</div>
<div class="modal">
<div class="modal-content">stuff2
<p class="close-modal">×</p>
</div>
</div>
<button type="button" class="pop-up">SEND</button>
<button type="button" class="pop-up">SEND</button>

For clarities sake, this is the solution that I have got working:
// Get the modal
var modals = document.getElementsByClassName('modal');
var btns = document.getElementsByClassName("pop-up");
var spans=document.getElementsByClassName("close-modal");
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function() {
modals[i].style.display = "block";
})
}
for (let i = 0; i < spans.length; i++) {
spans[i].addEventListener('click', function() {
modals[i].style.display = "none";
})
}
for (let i = 0; i < btns.length; i++) {
modals[i].addEventListener('click', function() {
if (event.target == modals[i]) {
modals[i].style.display = "none";
}
})
}
This seems to work as intended and as far as I am aware there is no issue with creating a listener for everything.

Related

Why page reloaded after the table is created by JavaScript

There is a dropdown list inside <div> element.
When an item in the dropdown list is clicked, a table should be created.
However, it is found that when the table is created, the page seems refreshed.
How to keep the result ?
<style>
#map {
margin: 10px 10px;
width: 560px;
height: 560px;
border: 1px solid #000000;
}
#dropdown_1 {
margin: 10px 10px;
position: absolute;
top: 0;
left: 600px;
width: 50px;
height: 10px;
/*border: 1px solid #000000;*/
}
/* Dropdown Button */
.dropbtn {
background-color: #3498DB;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
}
/* Dropdown button on hover & focus */
.dropbtn:hover, .dropbtn:focus {
background-color: #2980B9;
}
/* The container <div> - needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd}
/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
.show {display:block;}
</style>
<div id = "dropdown_1" class="dropdown">
<button onclick="dropdown_toggle()" class="dropbtn">Dropdown</button>
<div id="myDropdown" class="dropdown-content">
</div>
</div>
<script src="myscripts.js">
</script>
<script>
// document.addEventListener('keypress', logKey);
set_drop_down_list();
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function dropdown_toggle() {
document.getElementById("myDropdown").classList.toggle("show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
// console.log('hit-hit');
// creat_traffic_table();
}
}
}
}
</script>
In myscript.js :
function set_drop_down_list(){
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var img = new Image();
img.src = './pic/mps.png';
img.onload = () => {ctx.drawImage(img, 0, 0);};
var select = document.getElementById("myDropdown");
var options = ["Ma Wan", "Kap Shui Mun", "North Fairway", "Ha Pang", "Fairway Junction"];
for(var i = 0; i < options.length; i++) {
var opt = options[i];
var el = document.createElement("a");
el.textContent = opt;
el.value = opt;
el.setAttribute("href", "");
el.onclick = function(event) {
creat_traffic_table();
}
select.appendChild(el);
}
}
function create_traffic_table(){
var myArray = [
{'name':'Michael', 'age':'30', 'birthdate':'11/10/1989'},
{'name':'Mila', 'age':'32', 'birthdate':'10/1/1989'},
{'name':'Paul', 'age':'29', 'birthdate':'10/14/1990'},
{'name':'Dennis', 'age':'25', 'birthdate':'11/29/1993'},
{'name':'Tim', 'age':'27', 'birthdate':'3/12/1991'},
{'name':'Erik', 'age':'24', 'birthdate':'10/31/1995'},
]
const body = document.body;
tbl = document.createElement('table');
tbl.style.width = '100px';
tbl.style.position = 'absolute';
tbl.style.border = '1px solid black';
tbl.style.marginLeft = "800px";
tbl.style.marginTop = "50px";
for (var i = 0; i < myArray.length; i++){
var row = `<tr><td>${myArray[i].name}</td><td>${myArray[i].age}</td><td>${myArray[i].birthdate}</td></tr>`;
tbl.innerHTML += row;
}
body.appendChild(tbl);
}
I checked your code and I see, when looping, you added empty href attributes in every element in that dropdown menu, basically, you are telling them that when that element is clicked, refresh the page please.
el.setAttribute("href", "");
Remove that line, and you got yourself a fix to your problem.

Move li items on click between lists does not work properly

I have to lists and am using a jquery function to move items from one list (id="columns")to the other (id="columns1") when I click on the items in the first list (id="columns").
That part of it works fine, but when I add a new item in the first list (id="columns"), the previously moved items show up in the first list (id="columns").
Is there a way to prevent the moved items to show up in the first list? Is there a way to do it with vanilla js or do I need to use jquery?
This is all part of an to do list app I am creating where you could add tasks, remove them, click on the task to consider it a finished task, etc.
// Create a "close" button and append it to each list item
var myNodelist = document.getElementsByTagName("LI");
var i;
for (i = 0; i < myNodelist.length; i++) {
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.appendChild(txt);
myNodelist[i].appendChild(span);
}
// Click on a close button to hide the current list item
function closeEvent() {
var close = document.getElementsByClassName("close");
for (var i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
renderGraph();
}
}
}
// Add a "checked" symbol when clicking on a list item
var list = document.querySelector('ul');
list.addEventListener('click', function (ev) {
if (ev.target.tagName !== 'LI') return;
ev.target.classList.toggle('checked');
renderGraph();
}, false);
// Create a new list item when clicking on the "Add" button
function newElement() {
var li = document.createElement("li");
li.className = "column";
li.draggable = "true";
// order according to time
li.setAttribute("data-time", document.getElementById("myInput1").value);
// order according to time
var inputValue = document.getElementById("myInput").value;
var inputValue1 = document.getElementById("myInput1").value;
var tine = document.getElementById("myInput1");
tine.dateTime = "6:00"
// inputValue2.datetime = "6:00";
var tt = document.createTextNode(inputValue1 + " - ");
li.appendChild(tt);
var t = document.createTextNode(inputValue);
li.appendChild(t);
if (inputValue === '') {
alert("You must write a task!");
} else {
document.getElementById("columns").appendChild(li);
// order according to time start
setTimeout(function () {
var sortItems = document.querySelectorAll("[data-time]");
var elemArray = Array.from(sortItems);
elemArray.sort(function (a, b) {
if (a.getAttribute('data-time') < b.getAttribute('data-time')) { return -1 } else { return 1 }
});
//
document.getElementById("columns").innerHTML = "";
elemArray.forEach(appendFunction);
function appendFunction(item, index) {
document.getElementById("columns").innerHTML += item.outerHTML;
}
afterUpdate();
});
// order according to time end
}
document.getElementById("myInput").value = "";
document.getElementById("myInput1").value = "";
var span = document.createElement("SPAN");
var txt = document.createTextNode("\u00D7");
span.className = "close";
span.appendChild(txt);
li.appendChild(span);
for (i = 0; i < close.length; i++) {
close[i].onclick = function () {
var div = this.parentElement;
div.style.display = "none";
}
}
}
// Add tasks by pressing enter
// Get the input field
var input = document.getElementById("myInput");
// Execute a function when the user releases a key on the keyboard
input.addEventListener("keyup", function (event) {
// Number 13 is the "Enter" key on the keyboard
if (event.keyCode === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
document.getElementById("myBtn").click();
}
});
// //
// //
var btn = document.querySelector('.add');
var remove = document.querySelector('.column');
function dragStart(e) {
this.style.opacity = '0.4';
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
};
function dragEnter(e) {
this.classList.add('over');
}
function dragLeave(e) {
e.stopPropagation();
this.classList.remove('over');
}
function dragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
return false;
}
function dragDrop(e) {
if (dragSrcEl != this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
closeEvent();
}
return false;
}
function dragEnd(e) {
var listItems = document.querySelectorAll('.column');
[].forEach.call(listItems, function (item) {
item.classList.remove('over');
});
this.style.opacity = '1';
}
function addEventsDragAndDrop(el) {
el.addEventListener('dragstart', dragStart, false);
el.addEventListener('dragenter', dragEnter, false);
el.addEventListener('dragover', dragOver, false);
el.addEventListener('dragleave', dragLeave, false);
el.addEventListener('drop', dragDrop, false);
el.addEventListener('dragend', dragEnd, false);
}
function addDragAndDrop() {
var listItems = document.querySelectorAll('.column');
listItems.forEach(addEventsDragAndDrop);
}
afterUpdate();
function afterUpdate() {
closeEvent();
addDragAndDrop();
renderGraph();
}
function addNewItem() {
var newItem = document.querySelector('.input').value;
if (newItem != '') {
document.querySelector('.input').value = '';
var li = document.createElement('li');
var attr = document.createAttribute('column');
var ul = document.querySelector('ul');
li.className = 'column';
attr.value = 'true';
li.setAttributeNode(attr);
li.appendChild(document.createTextNode(newItem));
ul.appendChild(li);
addEventsDragAndDrop(li);
}
}
#myInput1 {
width: 180px;
height: 36px;
margin-right: 10px;
/* margin-left: -40px; */
/* padding: 10px; */
/* color: red; */
/* box-sizing: border-box; */
/* background-color: blue; */
/* display: inline-block; */
}
[draggable] {
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
user-select: none;
/* Required to make elements draggable in old WebKit */
-khtml-user-drag: element;
-webkit-user-drag: element;
}
/* Include the padding and border in an element's total width and height */
* {
box-sizing: border-box;
font-family: 'Josefin Sans', sans-serif;
}
p {
font-weight: 300;
}
h1 {
font-weight: 300;
}
#x-text {
color: #f97350;
font-size: 1.5rem;
}
::placeholder{
color: #777d71;
}
#myInput1:before {
content:'Time:';
margin-right:.6em;
color: #777d71;
}
/* Remove margins and padding from the list */
ul {
margin: 0;
padding: 0;
list-style: none;
}
/* Style the list items */
ul li {
cursor: pointer;
position: relative;
padding: 12px 8px 12px 40px;
background: #f7f6e7;
font-size: 18px;
transition: 0.2s;
color: rgb(94, 91, 91);
/* make the list items unselectable */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Set all odd list items to a different color (zebra-stripes) */
ul li:nth-child(odd) {
background: #dfddc5;
}
/* Darker background-color on hover */
ul li:hover {
background: rgb(207, 205, 205);
}
/* When clicked on, add a background color and strike out text */
ul li.checked {
background: #888;
color: #fff;
text-decoration: line-through;
}
/* Add a "checked" mark when clicked on */
ul li.checked::before {
content: '';
position: absolute;
border-color: #fff;
border-style: solid;
border-width: 0 2px 2px 0;
top: 10px;
left: 16px;
transform: rotate(45deg);
height: 15px;
width: 7px;
}
/* Style the close button */
.close {
position: absolute;
right: 0;
top: 0;
padding: 12px 16px 12px 16px;
color: #f97350;
/* font-size: 1.5rem; */
}
.close:hover {
background-color: #f97350;
color: white;
}
/* Style the header */
.header {
background-color: #777d71;
padding: 30px 40px;
color: white;
text-align: center;
}
/* Clear floats after the header */
.header:after {
content: "";
display: table;
clear: both;
}
/* Style the input */
input {
margin: 0;
border: none;
border-radius: 0;
width: 75%;
padding: 10px;
float: left;
font-size: 16px;
}
/* Style the "Add" button */
/* .addBtn {
padding: 10px;
width: 25%;
background: #d9d9d9;
color: #555;
float: left;
text-align: center;
font-size: 16px;
cursor: pointer;
transition: 0.3s;
border-radius: 0;
} */
/* .addBtn:hover {
background-color: #bbb;
} */
#finished-tasks {
/* box-sizing: border-box; */
padding: 20px;
display: grid;
align-content: center;
justify-content: center;
background-color:#bbd38b ;
color: white;
margin-bottom: -20px;
margin-top: 40px;
}
#finished-tasks-p {
/* box-sizing: border-box; */
padding: 20px;
display: grid;
align-content: center;
justify-content: center;
background-color:#bbd38b ;
color: white;
margin-bottom: 0;
margin-top: 0;
}
/* #myChart{
margin-top: 50px;
width: 50vw;
height: 100px;
padding-left: 200px;
padding-right: 200px;
} */
/* canvas{
width:1000px !important;
height:auto !important;
margin: auto;
} */
#media only screen and (max-width: 855px){
input {
margin: 10px;
}
#myInput {
display: grid;
width:70vw;
/* align-content: center; */
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="myDIV" class="header">
<h1>My Daily Tasks</h1>
<p>Add a time and task then press enter. When finished task click on task bar</p>
<p>To delete task click on <span id="x-text">x</span> in the corner of task bar</p>
<input type="time" id="myInput1" value="06:00">
<input name="text" type="text" id="myInput" placeholder="My task...">
<span onclick="newElement()" class="addBtn" id="myBtn"></span>
</div>
<ul id="columns">
<!-- <li draggable="true" class="column">test</li>
<li draggable="true" class="column">test1</li> -->
<!-- <li class="column" draggable="true">w</li>
<li class="column" draggable="true">ff</li>
<li class="column" draggable="true">uuu</li> -->
</ul>
<ul id="columns2">
<h1 id="finished-tasks">finished Tasks</h1>
<p id="finished-tasks-p">Finished tasks would show up here once you have clicked on the task</p>
</ul>
<!-- adding a graph -->
<canvas id="myChart" width="400" height="400"></canvas>
<script src="/graph.js"></script>
<script src="/app.js"></script>
<!--
<div id="element"></div>
<script>
document.getElementById('element').innerHTML = 'Hi';
</script>-->
<script>
$("#columns").on('click', 'li', function () {
$(this).appendTo('#columns2');
});
$("#listC").on('click', 'li', function () {
$(this).appendTo('#listB');
});
</script>
Change your setTimeout call to this:
setTimeout(function () {
var sortItems = Array.from(document.querySelectorAll("[data-time]"))
.filter((item) => !item.classList.contains('checked'))
.sort(function (a, b) {
if (a.getAttribute('data-time') < b.getAttribute('data-time')) { return -1 } else { return 1 }
});
document.getElementById("columns").innerHTML = "";
sortItems.forEach(appendFunction);
function appendFunction(item, index) {
document.getElementById("columns").innerHTML += item.outerHTML;
}
afterUpdate();
});
The key passage is here:
.filter((item) => !item.classList.contains('checked'))
Before, you were selecting all of the items, even the ones that were already checked. This filters those out.
Here's a working JSFiddle you can experiment with. I had to add an event listener to the addBtn to get it to work on there, but your original script is fine when running from my local machine.

How to induce some function when onmouseleave and also click the left mouse button?

I have got code like below:
Now I would like to add functionality that increase the counter not only when I leave the box/square, BUT ALSO click the left mouse button outside the box/square. So the counter will be increasing only when the mouse leave the box/square and also click outside of the box/square.
var counter = 0;
function myLeaveFunction() {
document.getElementById("demo").innerHTML = counter += 1;
}
div {
width: 100px;
height: 100px;
border: 1px solid black;
margin: 10px;
float: left;
padding: 30px;
text-align: center;
background-color: lightgray;
}
<div onmouseleave="myLeaveFunction()">
<p>onmouseleave: <br> <span id="demo">Mouse over and leave me AND CLICK OUTSIDE THE BOX!</span></p>
</div>
Add a boolean to check if mouse had left the div and bind a click handler to the document:
var counter = 0;
var mouseLeft = false;
function myLeaveFunction() {
document.getElementById("demo").innerHTML = counter+=1;
mouseLeft = true;
}
function clickBody() {
if (mouseLeft == true)
document.getElementById("demo").innerHTML = counter+=1;
mouseLeft = false;
}
document.onclick = clickBody;
This example will add an event listener to the body when the mouse leaves the box and will increase the counter when a click event occurs. The new event listener is then removed:
var counter = 0;
function myLeaveFunction() {
document.body.addEventListener("click", myClickHandler);
function myClickHandler() {
document.getElementById("demo").innerHTML = counter += 1;
document.body.removeEventListener("click", myClickHandler);
}
}
body {
margin: 0;
height: 100vh;
width: 100%;
}
div {
width: 100px;
height: 100px;
border: 1px solid black;
margin: 10px;
float: left;
padding: 30px;
text-align: center;
background-color: lightgray;
}
<body>
<div onmouseleave="myLeaveFunction()" onclick="event.stopPropagation()">
<p>onmouseleave: <br> <span id="demo">Mouse over and leave me AND CLICK OUTSIDE THE BOX!</span></p>
</div>
</body>
JSFiddle if this doesn't work
Like this?
Once you left the box AND you click outside of it. The counter will be increased.
var showPopup = function (id) {
document.getElementById(id).style.display = "block";
}
var hidePopup = function (id) {
document.getElementById(id).style.display = "none";
}
// Button Click
document.getElementById('myButton').onclick = (ev) => {
showPopup('myPopup');
ev.stopPropagation(); // prevent immediate close (because button is "outside" too)
}
// Click outside to close popup
document.getElementsByTagName('body')[0].onclick = (ev) => {
let popup = document.getElementById('myPopup');
if( ev.path.indexOf(popup) == -1 ) { hidePopup('myPopup') }
};
hidePopup('myPopup');
body {
width: 100vw;
height: 100vh;
}
div {
width: 100px;
height: 100px;
border: 1px solid black;
padding: 30px;
text-align: center;
background-color: lightgray;
position: absolute;
margin: 10px;
display: none;
}
<div id='myPopup'>
<p>onmouseleave: <br> <span id="demo">Mouse over and leave me AND CLICK OUTSIDE THE BOX!</span></p>
</div>
<button id='myButton'>Show!</button>
Take a look
This may be helful too Bootstrap Modal

Closing dropdown by clicking outside in Javascript (tutorial clarification)

I have attempted to implement the method of opening and closing a drop-down using Javascript via this tutorial on w3schools.com. While the function to "show" the drop-down works, the one to close it does not. Furthermore, there is no explanation alongside this code to explain why it should work, making it difficult to debug.
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("show");
}
// Close the dropdown menu if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
My questions are, therefore,
1) whether the code in the tutorial should work for the purpose of closing the drop-down. (ANSWERED)
2) Could someone please clarify how/why this should work, for the sake of clarity for myself and future newbies who make come across the same tutorial and issue? (UNANSWERED)
Edit (MY ATTEMPT):
HTML:
<div class="sharedown">
<p onclick="shareVis()" class="sharebtn">&nbsp Share</p>
<div id="mySharedown" class="sharedown-content">
Self
<p>User</p><input type="text" name="user-name" placeholder="Share to">
Community
</div>
</div>
JS:
function shareVis() {
document.getElementById("mySharedown").className = "show";
}
window.onclick = function(event) {
if (!event.target.matches('sharebtn')) {
var sharedowns = document.getElementsByClassName("sharedown-content");
var i;
for (i = 0; i < sharedowns.length; i++) {
var openSharedown = sharedowns[i];
if (openSharedown.classList.contains('show')) {
openSharedown.classList.remove('show');
}
}
}
}
CSS:
/* Share dropdown menu */
p.sharebtn {
color: darkgrey;
font-family:calibri;
padding: 0px;
margin: 0px;
font-size: 12;
border: none;
cursor: pointer;
display: inline;
}
/* Dropdown button on hover & focus */
p.sharebtn:hover, p.sharebtn:focus {
color: grey;
}
/* The container <div> - needed to position the dropdown content */
.sharedown {
position: relative;
display: inline-block;
}
/* Dropdown Content (Hidden by Default) */
.sharedown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 100px;
box-shadow: 0 2px 4px 1px #C4E3F5;
z-index:1; /* place dropdown infront of everything else*/
}
.sharedown-content a {
color: black;
padding: 5px 5px;
text-decoration: none;
display: block;
}
/* Show the dropdown menu (use JS to add this class to the .dropdown-
content container when the user clicks on the dropdown button) */
.show {display: block;
position: absolute;
background-color: #f1f1f1;
min-width: 100px;
box-shadow: 0 2px 4px 1px #C4E3F5;
opacity: 1;
z-index:1;}
The issue lies in shareVis function. Here
document.getElementById("mySharedown").className = "show";
you are replacing #mySharedown class name to show. Then in window.onclick
var sharedowns = document.getElementsByClassName("sharedown-content");
you are not getting any sharedowns as you already replaced the class name to show.
You can either add show class into classList
document.getElementById("mySharedown").classList.add("show");
or replace the class name with sharedown-content show
document.getElementById("mySharedown").className = "sharedown-content show";
Working solution below:
function shareVis() {
//document.getElementById("mySharedown").className = "sharedown-content show";
document.getElementById("mySharedown").classList.add("show");
}
window.onclick = function(event) {
if (!event.target.matches('.sharebtn')) {
var sharedowns = document.getElementsByClassName("sharedown-content");
var i;
for (i = 0; i < sharedowns.length; i++) {
var openSharedown = sharedowns[i];
if (openSharedown.classList.contains('show')) {
openSharedown.classList.remove('show');
}
}
}
}
document.getElementById("mySharedown").addEventListener('click',function(event){
event.stopPropagation();
});
#mySharedown{
display: none;
border: 1px solid black;
}
#mySharedown.show {
display: block;
}
<div class="sharedown">
<p onclick="shareVis()" class="sharebtn">&nbsp Share</p>
<div id="mySharedown" class="sharedown-content">
Self
<p>User</p><input type="text" name="user-name" placeholder="Share to">
Community
</div>
</div>
Update
To prevent the second click within #mySharedown from hiding #mySharedown, you should add another click event for #mySharedown and prevent it from bubbling up, like this
document.getElementById("mySharedown").addEventListener('click',function(event){
event.stopPropagation();
});
Updates are included in the working solution
Update 2022
Vanilla Javascript now contains a mehtod called Node.closest(Node) to check if the event matches the node in the upper hierarchy. below is an example to open the dropdown menu on click and hide it again on click and if clicking outside the document will also hide the dropdown menu.
const list = document.querySelector('.list')
const btn = document.querySelector('.btn')
btn.addEventListener('click', (e)=> {
list.classList.toggle('hidden')
e.stopPropagation()
})
document.addEventListener('click', (e)=> {
if(e.target.closest('.list')) return
list.classList.add('hidden')
})
.hidden {
display:none
}
ul {
background-color: blue;
}
<button class="btn">open</button>
<ul class="list hidden">
<li class="item1">Item 1</li>
<li class="item2">Item 2</li>
<li class="item3">Item 3</li>
</ul>
Here I leave another "short" example that I implemented to my own code, but is easy to understand.
.tw-hidden is a class "display: none"
window.onclick = function(event) {
let customDropdownsEl = document.querySelectorAll(".custom-dropdown");
let liContainerEl = event.target.querySelector(".custom-dropdown");
customDropdownsEl.forEach(el => el.parentNode !== event.target && !el.classList.contains("tw-hidden") && el.classList.add("tw-hidden"));
event.target.matches('.custom-dropdown-container') && liContainerEl.classList.toggle("tw-hidden");
}
The example is fully functional and should work. Copy the following code below:
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
.dropbtn {
background-color: #3498DB;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
}
.dropbtn:hover, .dropbtn:focus {
background-color: #2980B9;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown a:hover {background-color: #ddd}
.show {display:block;}
<h2>Clickable Dropdown</h2>
<p>Click on the button to open the dropdown menu.</p>
<div class="dropdown">
<button onclick="myFunction()" class="dropbtn">Dropdown</button>
<div id="myDropdown" class="dropdown-content">
Home
About
Contact
</div>
</div>

How to open a modal image for multiple images

on w3schools site i found a tutorial for a dialog box/popup window. I would like to implement this with more images.
my code:
<img onclick="openModal()" src="low/dn-64.jpg" data-highres="high/dn-64.jpg" width="300" height="400">
<!-- Modal -->
<div id="myModal" class="modal">
<!-- Close button -->
<span class="close">×</span>
<!-- Modal content -->
<img class="modal-content" id="modal-image">
</div>
<script type="text/javascript">
var modal = document.getElementById('myModal');
var img = document.getElementById('myImg');
var modalImg = document.getElementById('modal-image');
function openModal() {
modal.style.display = "block";
modalImg.src = this.getAttribute("data-highres");
}
/*img.onclick = function() {
modal.style.display = "block";
modalImg.src = this.getAttribute("data-highres");
}*/
var span = document.getElementsByClassName("close")[0];
span.onclick = function() {
modal.style.display = "none";
}
window.onclick = function() {
if(event.target == modal) {
modal.style.display = "none";
}
}
</script>
css:
#myImg {
cursor: zoom-in;
transition: 0.3s;
}
#myImg:hover {
opacity: 0.7;
}
.modal {
display: none;
position: fixed;
z-index: 1;
padding-top: 60px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.8);
}
.modal-content {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
}
.close {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 50px;
font-weight: bold;
transition: 0.3s;
}
.close:hover,
.close:focus {
color: #bbbbbb;
text-decoration: none;
cursor: pointer;
}
the code is working for one image. so i added a function openModal() and uncomment some part of the previous code. But now this isn't working even for one image it opens the modal without the image.
thank you for your help
The error in the console is Uncaught ReferenceError: openModal is not defined. This means the function was run at a scope in which it was not defined. an easy fix would be to define it in a global scope, like window:
window.openModal = function(img) {
modal.style.display = "block";
modalImg.src = img.getAttribute("data-highres");
}
and then
<img onclick="openModal(this);" src="low/dn-64.jpg" data-highres="high/dn-64.jpg" width="300" height="400">
However, you should not use not use onclick anymore (w3schools is a bit old now) like this answer says.
What you want to do is attach an event listener to your images. Loop through all of your images and use the addEventListener('click' ...) to listen when they were clicked.
var images = document.querySelectorAll('img');
for(var i=0, len = images.length; i < len; i++){
images[i].addEventListener('click', openModal);
}

Categories

Resources