I'm working on an interface which, when the user hovers over an LI element 2 buttons become visible to offer more functionality. Let's say "edit" and "remove".
I'm having issues with the mouse hit zones.
Mouseover works well when the mouse is actually on the LI element (the grey background) but does not work when the mouse is over text or the actual buttons which need to be clicked.
(For context, this is within a Chrome extension and no in-line JS is allowed. )
Summary demo here: https://jsfiddle.net/matthewzammit/uh9abfcr/16/
thanks!
Matt
-FULL CODE-
HTML
<div id="populateArrayList">
<ul id="myList">
<!--dynamically fill list-->
<li id="0" class="urlElement" name="urlElement">
<div class="urlDetails">
<div class="urlFavicon">
<img src="https://www.google.com/s2/favicons?domain=facebook.com">
</div>
<div name="url" class="urlAddress" id="0">website1.com</div>
</div>
<div class="actionButtons">
<a id="remove0" class="removeButton" name="Remove" href="#"></a>
<a id="edit0" class="editButton" name="Edit" href="#"></a>
</div>
</li>
<!-- item 2 -->
<li id="1" class="urlElement" name="urlElement">
<div class="urlDetails">
<div class="urlFavicon">
<img src="https://www.google.com/s2/favicons?domain=google.com">
</div>
<div name="url" class="urlAddress" id="1">website2.com</div>
</div>
<div class="actionButtons">
<a id="remove1" class="removeButton" name="Remove" href="#"></a>
<a id="edit1" class="editButton" name="Edit" href="#"></a>
</div>
</li>
</ul>
</div>
JS
document.getElementById("myList").addEventListener("mouseover", e => {
e.target.querySelector('.actionButtons').classList.add("editVisibility");
console.log("Target: " + e.target);
})
document.getElementById("myList").addEventListener("mouseout", function (e) {
e.target.querySelector('.actionButtons').classList.remove("editVisibility");
})
document.getElementById("myList").addEventListener("click", function (e) {
if (e.target && e.target.getAttribute('name') == "Remove") {
let idToRemove = e.target.parentElement.parentElement.getAttribute('id');
alert("Remove");
}
if (e.target && e.target.getAttribute('name') == "Edit") {
let idToEdit = e.target.parentElement.parentElement.getAttribute('id');
alert("Edit");
}
});
CSS
.urlElement {
list-style-type: none;
background: #ebebeb;
margin: 10px;
padding: 10px;
border-radius: 7px;
height: 15px;
}
.urlDetails {
/* background-color: red; */
}
.urlFavicon {
float: left;
margin-right: 10px;
/* background-color: green; */
}
.urlAddress {
float: left;
/* background-color: blue; */
}
.actionButtons {
display: none;
}
/* temp icon */
.removeButton {
margin-left: 5px;
padding-right: 20px;
background-image: url("https://img.icons8.com/ios/15/000000/delete-sign.png");
background-repeat: no-repeat;
}
/* temp icon */
.editButton {
margin-left: 5px;
padding-right: 20px;
background-image: url("https://img.icons8.com/ios/15/000000/edit.png");
background-repeat: no-repeat;
/* display: none;*/
}
.editVisibility {
display: unset;
/* Hide button */
}
You can do this with just CSS like this:
.urlElement:hover .actionButtons {
display: block;
}
You can delete the javascript event handlers.
Related
I have a menu with dropdown submenus. I'm trying to close an item when clicking on another, so that I don't have multiple items open at the same time. In a previous question: How to Apply .nextElementSibling to the next item of a dropdown menu a user suggested that I take a look at the function called mutually exclusivity. How can I add it to my menu?
var dropdownBtn = document.querySelectorAll('.menu-btn');
dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
var menuContent = this.nextElementSibling;
menuContent.classList.toggle("show");
}));
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
display: none;
background-color: #017575;
transition: 0.3s;
opacity: 0;
}
.drop_container.show {
display: contents;
visibility: visible;
opacity: 1;
}
.drop_container > .item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
Store previously clicked menu in a variable, and clear it's class if another menu was clicked
var dropdownBtn = document.querySelectorAll('.menu-btn'),
lastOpened = null;
dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
var menuContent = this.nextElementSibling;
menuContent.classList.toggle("show");
if (lastOpened && lastOpened !== menuContent)
lastOpened.classList.remove("show");
lastOpened = menuContent;
}));
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
display: none;
background-color: #017575;
transition: 0.3s;
opacity: 0;
}
.drop_container.show {
display: contents;
visibility: visible;
opacity: 1;
}
.drop_container > .item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
You could add a function that closes all menu's except the one you pass into it:
var dropdownBtn = document.querySelectorAll('.menu-btn');
dropdownBtn.forEach(btn => btn.addEventListener('click', function() {
var menuContent = this.nextElementSibling;
closeMenusExcept(menuContent);
menuContent.classList.toggle("show");
}));
function closeMenusExcept(menuContent) {
dropdownBtn.forEach((element) => {
if (menuContent !== element.nextElementSibling) {
element.nextElementSibling.classList.remove("show");
}
})
}
.menu-btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.menu-btn:hover {
background: #000;
color: #fff;
}
.drop_container {
display: none;
background-color: #017575;
transition: 0.3s;
opacity: 0;
}
.drop_container.show {
display: contents;
visibility: visible;
opacity: 1;
}
.drop_container>.item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
<div class="dropdown-menu">
<div class="menu-btn">One</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="menu-btn">Two</div>
<div class="drop_container">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
Note
I changed the class names so they easier to type and read.
We'll use the programming paradigm call event delegation.
Bind event to an ancestor tag (a tag that holds all of the tags you want to control)
Figure I
const menu = document.querySelector(".dropdown");
menu.addEventListener('click', //...
Next, design an event handler that only reacts when the right tags are clicked
Figure II
//...
function(event) {
// This is the tag the user clicked
const clicked = event.target;
// Find .show
const current = document.querySelector('.show');
// See if the clicked tag has .show class
let state = clicked.matches('.show');
// Only react if the clicked tag has .btn class
if (clicked.matches('.btn')) {//...
Note: The .show class is now assigned to the .btn. See Figure IV
Figure III
//...
// if there already is a .show
if (current) {
// remove .show
current.classList.remove('show');
}
// if the clicked tag did not have .show previously...
if (!state) {
// ...add .show to it
clicked.classList.add("show");
}
}
});
In CSS this ruleset uses the adjacent sibling combinator which is equivalent to .nextElementSibling
Figure IV
/* .btn.show + .list <=that's the next sibling */
.show+.list {
display: block;
}
Removed visibility and opacity since original state is display:none which is a switch that inhibits any sort of transition (also removed). The display: content was changed to display: block. display: content doesn't have any standard behavior, when applied, the .items were black and white, but once replaced their original green returned. As a general rule dealing with CSS is if you don't see it being used in the examples, don't use it because there's probably a good reason why it isn't being used.
With this setup you never have to worry about how many button/items you have as long as it is inside the ancestor tag. Also, if you add any button/items dynamically, they do not need to be bound to the event. All you'll ever need is one event listener for each event you want to listen for.
const menu = document.querySelector(".dropdown");
const btns = document.querySelectorAll('.btn');
menu.addEventListener('click', function(event) {
const clicked = event.target;
const current = document.querySelector('.show');
let state = clicked.matches('.show');
if (clicked.matches('.btn')) {
if (current) {
current.classList.remove('show');
}
if (!state) {
clicked.classList.add("show");
}
}
});
.btn {
background: #e0e0e0;
padding: 10px;
margin: 5px 0px 0px 0px;
}
.btn:hover {
background: #000;
color: #fff;
}
.list {
display: none;
background-color: #017575;
}
.show+.list {
display: block;
}
.list>.item {
display: flex;
flex-direction: column;
margin-left: 10px;
padding: 10px 0px 0px 0px;
}
<div class="dropdown">
<div class="btn">One</div>
<div class="list">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="btn">Two</div>
<div class="list">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
<div class="btn">Three</div>
<div class="list">
<a class="item" href="#">Contact Us</a>
<a class="item" href="#">Visit Us</a>
</div>
</div>
I'm not sure if my approach is correct, but hopefully, it is. I have different sections that can be edited on a checkout process. So if the client clicks the edit button for the relevant section it should hide the info section then show the edit section. my thinking was to use data type because of trying to keep the javascript as dry as possible? so this is what I have but not to sure how to approach it.
the idea is on click to get the data-type of the clicked edit button and match it to the id of the section to be shown then add a class of show to it. I got it working but initially, I have to do a double click to fire it off and if I do a console log the "clicked" count climbs exponentially.
also, feels that having two functions is repetitive..?
function editDetails() {
let trigger = document.querySelectorAll('.edit-link');
trigger.forEach(function(click) {
click.addEventListener('click', function(e) {
const target = this.getAttribute('data-checkout');
const checkoutSection = document.getElementById('checkout-' + target);
const checkoutEditSection = document.getElementById('edit-checkout-' + target);
if (checkoutEditSection.classList) {
checkoutEditSection.classList.add('show');
}
if (checkoutSection.classList) {
checkoutSection.classList.add('hide');
}
e.preventDefault();
})
});
}
function cancelDetails() {
let trigger = document.querySelectorAll('.btn-cancel');
trigger.forEach(function(click) {
click.addEventListener('click', function(e) {
const target = this.getAttribute('data-checkout');
const checkoutSection = document.getElementById('checkout-' + target);
const checkoutEditSection = document.getElementById('edit-checkout-' + target);
if (checkoutEditSection.classList) {
checkoutEditSection.classList.remove('show');
}
if (checkoutSection.classList) {
checkoutSection.classList.remove('hide');
}
console.log(target);
e.preventDefault();
})
});
}
.edit-link {
display: inline-flex;
padding: 1rem 2rem;
color: white;
background: blue;
margin-bottom: 2rem;
margin-right: 1rem;
}
.info {
padding: 4em;
background: lightcoral;
margin-bottom: 1rem;
display: block;
}
.details {
padding: 4em;
background: #e7e7e7;
margin-bottom: 1rem;
display: none;
}
.show {
display: block;
}
.hide {
display: none;
}
.btn {
display: inline-flex;
padding: 1em 2em;
color: white;
background: black;
text-decoration: none;
}
<div class="container container--lg">
<a onclick="editDetails()" data-checkout="personal" class="edit-link">Edit Personal Details</a>
<a onclick="editDetails()" data-checkout="payment" class="edit-link">Edit Payment Details</a>
<div id="checkout-personal" class="contact-info info">this is the Personal Details content</div>
<div id="edit-checkout-personal" class="contact-info details">
this is the Personal Details content in <b>Edit mode</b>
<a class="btn btn-cancel" data-checkout="personal" onclick="cancelDetails()">cancel</a>
<a class="btn" onclick="saveDetails()">Save</a>
</div>
<div id="checkout-payment" class="payment-info info">this is the Payment Details content</div>
<div id="edit-checkout-payment" class="payment-info details">this is the Payment Details content in <b>Edit mode</b>
<a class="btn btn-cancel" data-checkout="payment" onclick="cancelDetails()">cancel</a>
<a class="btn" onclick="saveDetails()">Save</a></div>
</div>
Every time we click editDetails, you add a function to the click of all '.edit-link' - you need only to add that once and add it to the static container, then any click on the container will reveal the target in e.target
Delegate like this
document.querySelector(".container").addEventListener("click", function(e) {
const tgt = e.target;
if (tgt.classList.contains("edit-link") || tgt.classList.contains("btn-cancel")) {
e.preventDefault();
const cancel = tgt.classList.contains("btn-cancel");
const target = tgt.getAttribute('data-checkout');
const checkoutSection = document.getElementById('checkout-' + target);
const checkoutEditSection = document.getElementById('edit-checkout-' + target);
checkoutEditSection.classList.toggle('show', !cancel);
checkoutSection.classList.toggle('hide', cancel);
}
})
.edit-link {
display: inline-flex;
padding: 1rem 2rem;
color: white;
background: blue;
margin-bottom: 2rem;
margin-right: 1rem;
}
.info {
padding: 4em;
background: lightcoral;
margin-bottom: 1rem;
display: block;
}
.details {
padding: 4em;
background: #e7e7e7;
margin-bottom: 1rem;
display: none;
}
.show {
display: block;
}
.hide {
display: none;
}
.btn {
display: inline-flex;
padding: 1em 2em;
color: white;
background: black;
text-decoration: none;
}
<div class="container container--lg">
<a data-checkout="personal" class="edit-link">Edit Personal Details</a>
<a data-checkout="payment" class="edit-link">Edit Payment Details</a>
<div id="checkout-personal" class="contact-info info">
this is the Personal Details content
</div>
<div id="edit-checkout-personal" class="contact-info details">
this is the Personal Details content in <b>Edit mode</b>
<a class="btn btn-cancel" data-checkout="personal">cancel</a>
<a class="btn" onclick="saveDetails()">Save</a>
</div>
<div id="checkout-payment" class="payment-info info">
this is the Payment Details content
</div>
<div id="edit-checkout-payment" class="payment-info details">
this is the Payment Details content in <b>Edit mode</b>
<a class="btn btn-cancel" data-checkout="payment">cancel</a>
<a class="btn" onclick="saveDetails()">Save</a>
</div>
</div>
Not sure all your requirements but here I show how to add event handlers for each button and call a function to simply toggle the hide class.
Your other work for the buttons can be placed in the functions as appropriate;
function showAction(text) {
const showMe = document.getElementById('showme');
showMe.textContent = text;
}
function clickEventhandler(event) {
showAction('Clicked:' + this.textContent)
const target = this.getAttribute('data-checkout');
const checkoutSection = document.getElementById('checkout-' + target);
const checkoutEditSection = document.getElementById('edit-checkout-' + target);
checkoutSection.classList.toggle('hide');
checkoutEditSection.classList.toggle('hide');
event.preventDefault();
}
function saveEventHandler(event) {
showAction('Clicked:' + this.textContent);
clickEventhandler.call(this, event);
}
function cancelEventHandler(event) {
showAction('Clicked:' + this.textContent);
clickEventhandler.call(this, event);
}
function cancelEventHandler(event) {
showAction('Clicked:' + this.textContent);
clickEventhandler.call(this, event);
}
document.querySelectorAll('.edit-link')
.forEach(function(element) {
element.addEventListener('click', clickEventhandler);
});
document.querySelectorAll('.btn-cancel')
.forEach(function(element) {
element.addEventListener('click', cancelEventHandler);
});
document.querySelectorAll(".save-details")
.forEach(function(element) {
element.addEventListener('click', saveEventHandler);
});
.edit-link {
display: inline-flex;
padding: 1rem 2rem;
color: white;
background: blue;
margin-bottom: 2rem;
margin-right: 1rem;
}
.info {
padding: 4em;
background: lightcoral;
margin-bottom: 1rem;
}
.details {
padding: 4em;
background: #e7e7e7;
margin-bottom: 1rem;
}
.hide {
display: none;
}
.btn {
display: inline-flex;
padding: 1em 2em;
color: white;
background: black;
text-decoration: none;
}
#showMe {
border solid lime 1px;
}
<div class="container container--lg">
<a data-checkout="personal" class="edit-link">Edit Personal Details</a>
<a data-checkout="payment" class="edit-link">Edit Payment Details</a>
<div id="checkout-personal" class="contact-info info ">this is the Personal Details content</div>
<div id="edit-checkout-personal" class="contact-info details hide">
this is the Personal Details content in <b>Edit mode</b>
<a class="btn btn-cancel" data-checkout="personal">cancel</a>
<a class="btn save-details" data-checkout="personal">Save</a>
</div>
<div id="checkout-payment" class="payment-info info">this is the Payment Details content</div>
<div id="edit-checkout-payment" class="payment-info details hide">this is the Payment Details content in <b>Edit mode</b>
<a class="btn btn-cancel" data-checkout="payment">cancel</a>
<a class="btn save-details" data-checkout="payment">Save</a></div>
</div>
<div id="showme"></div>
document.addEventListener('DOMContentLoaded', function() {
var add = document.getElementById('addButton');
var table = document.getElementById('table');
var deleteBtn = document.querySelectorAll('.deleteButton');
add.addEventListener('click', (e) => {
table.innerHTML += `<div class="row">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span class="deleteButton"><i class="fa fa-trash" aria-hidden="true"></i></span>
</div>`;
});
deleteBtn.forEach(function(btn) {
btn.addEventListener('click', function() {
console.log(this);
})
})
});
body {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.table-wrapper {
/* width: 90vw;
max-width: 900px;
min-height: 80px; */
width: 600px;
background-color: white;
border: 1px solid #e6edf0;
max-height: 400px;
overflow-y: auto;
}
.table-wrapper>.row {
height: 40px;
width: 100%;
border-bottom: 1px solid #ecf1f4
}
.table-wrapper>.row.header {
background-color: #ecf1f4;
font-weight: lighter;
color: #b0b0b0;
text-transform: uppercase;
}
.table-wrapper>.row>span {
display: inline-block;
line-height: 40px;
text-align: center;
font-size: 12px;
position: relative;
}
.table-wrapper>.row>span:nth-child(1) {
width: 40px;
}
.table-wrapper>.row>span:nth-child(2) {
width: 80px;
}
.table-wrapper>.row>span:nth-child(3) {
width: 60px;
}
.table-wrapper>.row>span:nth-child(4) {
width: 60px;
}
.table-wrapper>.row>span:nth-child(5) {
width: 80px;
}
.table-wrapper>.row>span:nth-child(6) {
width: 40px;
color: #c0c0c0;
}
.table-wrapper input[type=text],
.table-wrapper select {
height: 30px;
box-sizing: border-box;
max-width: 50px;
vertical-align: middle;
}
i.fa-trash {
cursor: pointer;
font-size: 16px;
}
.btn-wrapper {
width: 100%;
height: 40px;
background-color: white;
margin-top: 15px;
border: 1px solid #e6edf0;
box-sizing: border-box;
line-height: 40px;
text-align: center;
font-size: 13px;
cursor: pointer;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous>
<div class="wrapper">
<div id="table" class="table-wrapper">
<div class="row header">
<span class="">idx</span>
<span class="">name</span>
<span class="">type</span>
<span class="">options</span>
<span class="">output</span>
<span class=""> </span>
</div>
<div class="row">
<span class="">1</span>
<span class=""><input type="text" /></span>
<span class=""><select><option>Name</option></select></span>
<span class=""><select><option>Korean</option><option>English + Korean</option></select></span>
<span class="">Kim</span>
<span class="deleteButton"><i class="fa fa-trash" aria-hidden="true"></i></span>
</div>
</div>
<div id="addButton" class="btn-wrapper">
<i class="fa fa-plus" aria-hidden="true"></i> ADD ROW
</div>
</div>
I'm trying to develop with vanila JS.
And I'm making a table it is having add function and delete function.
I made add function successfully, but I can't make delete function about dynamic elements.
When I clicked ADD ROW button, one row is added to the table.
And I clicked delete icon, I want to see console.log(this).
But, it's not working.
Have you ever any idea to solve this with vanilla JS?
I know it is simple problem, so I want to say that I'm sorry and Thank you so much.
Summary
How can I add eventListener at dynamic elements efficiently?
What am I trying to do?
let say I want a list. I can add more element by clicking a button and if I click on a list item the title of the list should change.
The first approach is what you are doing in your code. The 2nd approach is what you should do to fix it.
Approach #1 - That won't work
You can find the reason below the code snippet why it won't work.
var title = document.getElementById("title"),
parent = document.getElementById("parent"),
child = document.querySelectorAll(".child"),
add = document.getElementById("add");
add.addEventListener("click", function() {
parent.innerHTML += '<li class="child">New element</li>';
});
child.forEach(function (child) {
child.addEventListener("click", function() {
title.innerHTML = this.innerHTML;
});
});
<h3 id="title">This is a title</h3>
<ul id="parent">
<li class="child">First element</li>
<li class="child">Second element</li>
<li class="child">Third element</li>
<li class="child">Fourth element</li>
</ul>
<button id="add">Add element</button>
This is known as Event delegation. When the page was first loaded the dynamically generated LI where not in the DOM and the click event was not attached to them. So try the code below which can solve that issue.
Approach #2 - That should work
var title = document.getElementById("title"),
parent = document.getElementById("parent"),
child = document.querySelectorAll(".child"),
add = document.getElementById("add"),
counter = 1;
add.addEventListener("click", function() {
parent.innerHTML += '<li class="child">New element ' + counter + '</li>';
counter++;
});
parent.addEventListener("click", function(e) {
if ( e.target.nodeName == "LI" ) {
title.innerHTML = e.target.innerHTML;
}
});
<h3 id="title">This is a title</h3>
<ul id="parent">
<li class="child">First element</li>
<li class="child">Second element</li>
<li class="child">Third element</li>
<li class="child">Fourth element</li>
</ul>
<button id="add">Add element</button>
Hope this example helps to understand event delegation. If any issue drops a comment below.
Use Event Delegation which is a procedure of leveraging event bubbling and the event object Event.target (commonly the element clicked, changed, hovered, etc.). The most efficient way to employ event delegation using plain JavaScript is to add an event listener to an ancestor element that all targeted elements have in common (it can be parentElement (mom), parentElement.parentElement (grandma), ..., even document or window).
Details commented in demo and Plunker
References located after demo
Demo
<!DOCTYPE html>
<html>
<head>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.table-wrapper {
/* width: 90vw;
max-width: 900px;
min-height: 80px; */
width: 600px;
background-color: white;
border: 1px solid #e6edf0;
max-height: 400px;
overflow-y: auto;
}
.table-wrapper>.row {
height: 40px;
width: 100%;
border-bottom: 1px solid #ecf1f4
}
.table-wrapper>.row.header {
background-color: #ecf1f4;
font-weight: lighter;
color: #b0b0b0;
text-transform: uppercase;
}
.table-wrapper>.row>span {
display: inline-block;
line-height: 40px;
text-align: center;
font-size: 12px;
position: relative;
}
.table-wrapper>.row>span:nth-child(1) {
width: 40px;
}
.table-wrapper>.row>span:nth-child(2) {
width: 80px;
}
.table-wrapper>.row>span:nth-child(3) {
width: 60px;
}
.table-wrapper>.row>span:nth-child(4) {
width: 60px;
}
.table-wrapper>.row>span:nth-child(5) {
width: 80px;
}
.table-wrapper>.row>span:nth-child(6) {
width: 40px;
color: #c0c0c0;
}
.table-wrapper input[type=text],
.table-wrapper select {
height: 30px;
box-sizing: border-box;
max-width: 50px;
vertical-align: middle;
}
i.fa-trash {
cursor: pointer;
font-size: 16px;
}
.btn-wrapper {
width: 100%;
height: 40px;
background-color: white;
margin-top: 15px;
border: 1px solid #e6edf0;
box-sizing: border-box;
line-height: 40px;
text-align: center;
font-size: 13px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="wrapper">
<div id="table" class="table-wrapper">
<div class="row headed">
<span class=" ">idx</span>
<span class=" ">name</span>
<span class=" ">type</span>
<span class=" ">options</span>
<span class=" ">output</span>
<span class=" "> </span>
</div>
<div class="row">
<span class=" ">1</span>
<span class=" "><input type="text" /></span>
<span class=" "><select><option>Name</option></select></span>
<span class=" "><select><option>Korean</option><option>English + Korean</option></select></span>
<span class=" ">Kim</span>
<span class="btn deleteButton"><i class="fa fa-trash" aria-hidden="true"></i></span>
</div>
</div>
<div id="addButton" class="btn btn-primary .btn-wrapper">
<i class="fa fa-plus" aria-hidden="true"></i> ADD ROW
</div>
</div>
<script>
var add = document.getElementById('addButton');
var table = document.getElementById('table');
add.addEventListener('click', function(e) {
/* insertAdjacentHTML() is like innerHTML on steroids
|| See references for details
*/
table.insertAdjacentHTML('beforeend', `<div class="row">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span class="btn deleteButton">
<i class="fa fa-trash" aria-hidden="true"></i>
</span>
</div>`);
});
/* Register table to click event...
== if e.target (the element clicked i.e. .fa-trash)
|| is NOT e.currentTarget
|| (the element registered to event i.e. #table)...
== if e.target has the class .fa-trash...
== Get the grandparent of e.target (i.e. .row)
== Remove the .row
*/
table.addEventListener('click', function(e) {
if (e.target !== e.currentTarget) {
if (e.target.classList.contains('fa-trash')) {
var row = e.target.parentElement.parentElement;
table.removeChild(row);
}
/* Prevent the event bubbling to keep
|| any unwanted elements from being
|| triggered
*/
e.stopPropagation();
}
}, false);
</script>
</body>
</html>
References
Event Delegation
insertAdjacentHTML()
Event.target
Event.currentTarget
After thoroughly trying to fix this issue - I need a little help.
I am trying to make a website that has a navbar (made with bootstrap) for websites and I am making a small drop-down menu for smaller screens (I haven't added this functionality yet, I just want it to work first). I haven't styled it much yet either.
The problem is that I know my button and code is working (because I have a codepen showing that it works), but in my website, I cannot see the drop-down menu. Not sure if it is hidden or what but I just can't figure this out.
Here is the HTML (because I have to put something...):
<div class = "dropdown">
<button onclick = "menuBtn ()" class = "dropBtn">Menu</button>
<div id = "dropCollapse" class = "dropdownContent">
<a class = "contentLinks" href = "#about">About</a>
<a class = "contentLinks" href = "#team">Team</a>
<a class = "contentLinks" href = "#photos">Photos</a>
<a class = "contentLinks" href = "#shirts">T-Shirts</a>
<a class = "contentLinks" href = "#contact">Contact</a>
</div>
</div>
I have played around with z-index (in a number of places but if you have a suggestion, feel free to make it and I will try it). I have taken the menu out of the navbar (thinking it had something to do with that). But mostly I am just confused - nothing else really answered my question about this menu issue. I feel like there is something small that I am overlooking and I just can't figure it out.
Here is a fiddle showing the basic outline of my website with the menu not working: https://jsfiddle.net/nekochan/eh69segg/1/
A few things I noticed:
you were using jQuery but did not define jQuery to load. If you do an "inspect element" you'll see that $ is not defined
also you had a few missing divs, and an anchor wasn't closed
I'd recommend just using purely jQuery if you're going to go that approach - it's a very simple example. Here is your updated fiddle - notice the toggle animates nicely :)
https://jsfiddle.net/qdL9mch2/1/
/* global $ */
$(document).ready(function() {
//makes the masethead fit the whole screen
$("#masthead").css("min-height", $(window).height());
//mobile menu button collapse
$(".dropBtn").on("click", function(){
$("#dropCollapse").toggle("show");
});
// Close the dropdown menu if the user clicks outside of it
//update to jquery
window.onclick = function(event) {
if (!event.target.matches('.dropBtn')) {
var dropdowns = document.getElementsByClassName("dropdownContent");
for (var i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
};
});
there are multiple errors there in your HTML for example <a class="navBrand" href="#masthead"> is not closing and your menuBtn is not being called, I would recommend you to use jquery completely if you have it included inside your project see here your code working
//mobile menu button collapse
function menuBtn() {
document.getElementById("dropCollapse").classList.toggle("show");
}
/* global $ */
$(document).ready(function() {
$("#my-button").click(function() {
document.getElementById("dropCollapse").classList.toggle("show");
})
//makes the masethead fit the whole screen
$("#masthead").css("min-height", $(window).height());
// Close the dropdown menu if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropBtn')) {
var dropdowns = document.getElementsByClassName("dropdownContent");
for (var i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
};
});
.main {
font-family: 'Roboto', sans-serif;
width: 100%;
margin: 0px;
padding: 0px;
overflow-x: hidden;
overflow-y: hidden;
}
.body {
position: relative;
}
#navBar {
margin-bottom: 0;
background-color: black;
font-family: 'Permanent Marker', cursive;
}
.brandImage {
height: 60px;
width: auto;
}
#navHeader {}
#navItem {}
#navLink {
text-decoration: none;
}
.dropBtn {
background-color: #4caf50;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
}
.dropBtn:hover,
.dropBtn:focus {
background-color: #3e8e41;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdownContent {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 9999;
}
.contentLinks {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.contentLinks:hover {
background-color: #f1f1f1;
}
.show {
display: block;
}
#media (max-width: 960px) {
#large-menu {
display: none;
}
.brandImage {
float: left;
}
}
#masthead {
background-color: #65737e;
background-image: url(https://static.pexels.com/photos/285286/pexels-photo-285286.jpeg);
width: 100%;
height: auto;
background-size: cover;
background-position: bottom center;
display: flex;
align-items: center;
min-height: 100%;
min-height: 100vh;
}
.headerText {
font-size: 90px;
font-family: 'Permanent Marker', cursive;
color: #fff;
}
.headerTagline {
font-size: 60px;
font-family: 'Permanent Marker', cursive;
color: #fff;
}
.anchor {
display: block;
height: 50px;
margin-top: -50px;
visibility: hidden;
}
.sectionHeader {
font-family: 'Permanent Marker', cursive;
padding: 20px 20px 20px 20px;
}
.sectionText {
padding: 20px 20px 20px 20px;
}
#aboutBox {
background-color: #c0c5ce;
padding: 20px 0 20px;
}
#teamBox {
background-color: #a7adba;
padding: 20px 0 20px;
}
#workBox {
background-color: #65737e;
padding: 20px 0 20px;
}
#shirtBox {
background-color: #4f5b66;
padding: 20px 0 20px;
}
#socialBox {
background-color: #343d46;
padding: 20px 0 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="main">
<nav class="navbar navbar-inverse navbar-fixed-top" id="navBar">
<div class="container-fluid">
<div class="navHeader navbar-left">
<a class="navBrand" href="#masthead"></a>
<ul class="nav navbar-nav navbar-right" id="large-menu">
<li class="navItem">
<a class="navLink" href="#about">About</a>
</li>
<li class="navItem">
<a class="navLink" href="#team">Team</a>
</li>
<li class="navItem">
<a class="navLink" href="#photos">Photos</a>
</li>
<li class="navItem">
<a class="navItem" href="#shirts">T-Shirts</a>
</li>
<li class="navItem">
<a class="navLink" href="#contact">Contact</a>
</li>
</ul>
</div>
<div class="dropdown">
<button class="dropBtn" id="my-button">Menu</button>
<div id="dropCollapse" class="dropdownContent">
<a class="contentLinks" href="#about">About</a>
<a class="contentLinks" href="#team">Team</a>
<a class="contentLinks" href="#photos">Photos</a>
<a class="contentLinks" href="#shirts">T-Shirts</a>
<a class="contentLinks" href="#contact">Contact</a>
</div>
</div>
</nav>
<div class="container text-center" id="masthead">
<div class="col-sm-12">
<h1 class="headerText"></h1>
<p class="headerTagline"></p>
</div>
</div>
<span class="anchor" id="about"></span>
<div class="container-fluid" id="aboutBox">
<div class="row">
<div class="col-sm-12">
<h2 class="sectionHeader">About Us</h2>
<p class="sectionText">We're all about that chedda</p>
</div>
</div>
</div>
<span class="anchor" id="team"></span>
<div class="container-fluid" id="teamBox">
<div class="row">
<div class="col-sm-12">
<h2 class="sectionHeader">Meet the team</h2>
<p class="sectionText">pictures of team go here</p>
</div>
</div>
</div>
<span class="anchor" id="photos"></span>
<div class="container-fluid" id="workBox">
<div class="row">
<div class="col-sm-12">
<h2 class="sectionHeader">Our Work</h2>
<p class="sectionText">pictures go here</p>
</div>
</div>
</div>
<span class="anchor" id="shirts"></span>
<div class="container-fluid" id="shirtBox">
<div class="row">
<div class="col-sm-12">
<h2 class="sectionHeader">T-shirts Preview</h2>
<p class="sectionText">pictures of tshirts go here</p>
</div>
</div>
</div>
<span class="anchor" id="contact"></span>
<div class="container-fluid" id="socialBox">
<div class="row">
<div class="col-sm-12">
<h2 class="sectionHeader">Contact Us</h2>
<p class="sectionText">links to social media and contact us form</p>
</div>
</div>
</div>
I want to create a dynamic popup menu that adds a class to "lists" parent when radio button is selected // removes classes when unselected (HTML structure below cannot change...only can change CSS and JS)
I'm trying to make the "List Popup" popup directly under each "Green Gear" icon when clicked on/ hidden when clicked off (css classes).
Whenever a radio button is selected from the popup, I need it to add a class directly into the corresponding list's parent div, while not effecting any of the other lists.
The trick (for me) is making all of this happen dynamically with 1 popup menu that sits outside of each lists divs scope.
The HTML structure cannot be changed (this has to be completely done w/ JS and CSS).
Here's what I've got so far: https://jsfiddle.net/oneeezy/t5eou67k/
$(document).ready(function () {
/* Code to play with for show/hide popup
/* Code to play with for adding/removing classes when radio checked
$('.list-layouts input').click(function () {
$('.list-layouts input:not(:checked)').parent().removeClass("blue");
$('.list-layouts input:checked').parent().addClass("blue");
});
*/
});
/* Reset Styles */
* { box-sizing: border-box; margin: 0; padding: 0; }
h1 { padding: 0 0 .25rem; }
h2 { line-height: 1.6; }
aside { background: #e2e4e6; padding: 1rem; color: gray; margin: 1rem; border: 1px dashed gray; }
aside ul { list-style: inside; }
aside span { font-style: italic; color: rgba(0, 0, 0, .78); }
.page { background: rgba(137, 96, 158, .17); margin: 2rem 0; padding: 2rem; position: relative; }
.page::before { content: "<div> ...Scope"; color: rgba(0, 0, 0, .54); position: absolute; top: 0; left: 0; }
.page::after { content: "</div>"; color: rgba(0, 0, 0, .54); position: absolute; bottom: 0; left: 0; }
.purple { background: rgba(137, 96, 158, .17); }
.green { background: lime; }
.yellow { background: yellow; }
/* Trello (default tyles) */
.wrapper { display: flex; }
.list-wrapper { flex: 1; margin: 10px; }
.list { background: #e2e4e6; position: relative; padding: 0 10px 3px; }
.icon { display: block; position: absolute; top: 0; right: 0; width: 33px; height: 38px; background: lime; text-decoration: none; font-size: 2em; line-height: 1.3; color: darkgreen; }
.card { display: block; height: 50px; background: white; border-radius: 3px; border: 1px solid #ccc; padding: .5em; margin: 0 0 .5em; }
/* List Popup */
.list-popup { display: block; width: 350px; max-height: 800px; background: yellow; border-radius: 3px; border: 1px solid #ccc; padding: 1em; }
.list-popup p { padding: 0 0 1rem; }
.list-popup p span::after { content: "<div class='list-wrapper'>"; }
.list-layouts ul { list-style: none; }
.list-normal { }
.list-normal .list { }
.list-color { }
.list-color .list * { background: skyblue; }
.list-bold { }
.list-bold .list * { font-weight: bold; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Description -->
<aside>
<h1>Dynamic popup menu that adds class to lists parent when radio button is selected // removes classes when unselected</h1>
<ul>
<li>I'm trying to make the <span class="yellow">"List Popup"</span> popup directly under each <span class="green">"Green Gear"</span> icon when clicked on/ hidden when clicked off (css classes).</li>
<li>Whenever a radio button is selected from the popup, I need it to add a class directly into the corresponding list's parent div, while not effecting any of the other lists.</li>
<li>The trick (for me) is making all of this happen dynamically with 1 popup menu that sits outside of each <span class="purple">lists divs scope</span>.</li>
</ul>
</aside>
<!-- Lists -->
<div class="page">
<div class="wrapper">
<div class="list-wrapper">
<div class="list">
<h2>List (1)</h2>
⚙
<div class="card">Woohoo! Woohoo! Woohoo! </div>
<div class="card">Woohoo! Woohoo! Woohoo! </div>
<div class="card">Woohoo! Woohoo! Woohoo! </div>
</div>
</div>
<div class="list-wrapper">
<div class="list">
<h2>List (2)</h2>
⚙
<div class="card">Woohoo! Woohoo! Woohoo! </div>
<div class="card">Woohoo! Woohoo! Woohoo! </div>
<div class="card">Woohoo! Woohoo! Woohoo! </div>
</div>
</div>
<div class="list-wrapper">
<div class="list">
<h2>List (3)</h2>
⚙
<div class="card">Woohoo! Woohoo! Woohoo! </div>
<div class="card">Woohoo! Woohoo! Woohoo! </div>
<div class="card">Woohoo! Woohoo! Woohoo! </div>
</div>
</div>
</div>
</div>
<!-- List Layout popup -->
<div class="page">
<div id="listPopup" class="list-popup">
<div>
<h3>List Popup</h3>
<p>These radio buttons should add a special class to the individual <span></span> when clicked on and removed when clicked off!</p>
</div>
<form class="list-layouts">
<ul>
<li>
<input type="radio" name="listLayout" id="listNormal">
<label for="listNormal">Normal</label>
</li>
<li>
<input type="radio" name="listLayout" id="listColor">
<label for="listColor">Bold</label>
</li>
<li>
<input type="radio" name="listLayout" id="listBold">
<label for="listBold">Italic</label>
</li>
</ul>
</form>
</div>
</div>
So first we will add the class to the list wrapper of the lists parent with the following code:
$('.icon').on('click', function () {
if(!$('#listPopup').hasClass("open")){
$('#listPopup').toggleClass("open");
}
$(this).parents('.list-wrapper').siblings().removeClass('blue');
if(!$(this).parents('.list-wrapper').hasClass("blue")){
$(this).parents('.list-wrapper').toggleClass("blue");
}
});
Then we need to create an onchange function for the radios to find this class and add the new class to that list.
$('.list-layouts input').on('change', function () {
var newClass = $(this).attr("id");
// Remove the following statement if you want your classes to stack up
$('.blue').removeClass("listNormal listColor listBold");
$('.blue').addClass(newClass);
});
Here is a working fiddle of the whole thing in action Fiddle