JS code breaks HTML checkbox element functionality - javascript

I have a custom select list which I did with checkbox and label elements.
Then I added a little JS script that changes options on click.
It works perfectly, except you can't close it on click outside of it or its child elements.
I tried to use event listener(commented in snippet) to track if the click was done outside and if it is - change checkbox status on 'false' and it kinda worked, however, it breaks the original checkbox functionality and you can't now close it in any way except for clicking outside of it. Is there a way I can fix this?
//SELECT FROM LIST
function selectList(id) {
let selected = document.getElementById('selected');
selected.innerHTML = id;
}
//BREAKS CHECKBOX
//window.addEventListener('mouseup', function(event){
// let checkbox = document.getElementById('checkbox');
// if (event.target != checkbox && event.target.parentNode != checkbox){
// checkbox.checked = false;
// }
// });
.btn {
display: block;
width: 100px;
height: 50px;
text-align: center;
color: white;
background: black;
border-radius: 15px;
line-height: 50px;
cursor: pointer;
user-select: none;
position: relative;
}
.btn:active {
background: grey;
}
.btn ul {
display: none;
list-style: none;
color: black;
flex-direction: column;
margin: 0;
padding: 2.5px 5px;
left: 0;
top: 50px;
background: grey;
border-radius: 15px;
position: absolute;
}
.btn ul li {
display: block;
width: 90px;
height: 35px;
line-height: 35px;
background: black;
border-radius: 15px;
color: white;
margin: 2.5px 0;
}
.btn ul li:active {
background: grey;
}
/* CHECKBOX CHEKCED */
#checkbox:checked + .btn ul {
display: flex;
}
<input type="checkbox" id="checkbox">
<label class="btn" for="checkbox">
<span id="selected">SELECT</span>
<ul>
<li onclick="selectList('Opt1')">Opt1</li>
<li onclick="selectList('Opt2')">Opt2</li>
<li onclick="selectList('Opt3')">Opt3</li>
</ul>
</label>

Your strategy uses css to style the state of the dropdown based on the radio option value.
So there's no event handler in place already catching that point in time and the only approach to close the dropdown when you click outside, was using a click event handler on the main document and check for the element triggering the event to be of type HTML. Not the best approach indeed. (I edited the question to use a better approach... there's a container having the class dropdown now and event.target gets checked for having an ancestor with such class to understand if the click was fired from the dropdown itself or outside of it)
document.addEventListener('click', function(event){
if(event.target.closest('.dropdown') === null){
document.querySelector('#checkbox').checked = false;
}
});
Anyway once there, the logic just force the radio option to unchecked and restore the dropdown collapse state.
//SELECT FROM LIST
function selectList(id) {
let selected = document.getElementById('selected');
selected.innerHTML = id;
}
document.addEventListener('click', function(event){
if(event.target.closest('.dropdown') === null){
document.querySelector('#checkbox').checked = false;
}
});
//BREAKS CHECKBOX
//window.addEventListener('mouseup', function(event){
// let checkbox = document.getElementById('checkbox');
// if (event.target != checkbox && event.target.parentNode != checkbox){
// checkbox.checked = false;
// }
// });
.btn {
display: block;
width: 100px;
height: 50px;
text-align: center;
color: white;
background: black;
border-radius: 15px;
line-height: 50px;
cursor: pointer;
user-select: none;
position: relative;
}
.btn:active {
background: grey;
}
.btn ul {
display: none;
list-style: none;
color: black;
flex-direction: column;
margin: 0;
padding: 2.5px 5px;
left: 0;
top: 50px;
background: grey;
border-radius: 15px;
position: absolute;
}
.btn ul li {
display: block;
width: 90px;
height: 35px;
line-height: 35px;
background: black;
border-radius: 15px;
color: white;
margin: 2.5px 0;
}
.btn ul li:active {
background: grey;
}
/* CHECKBOX CHEKCED */
#checkbox:checked + .btn ul {
display: flex;
}
<div class="dropdown">
<input type="checkbox" id="checkbox">
<label class="btn" for="checkbox">
<span id="selected">SELECT</span>
<ul>
<li onclick="selectList('Opt1')">Opt1</li>
<li onclick="selectList('Opt2')">Opt2</li>
<li onclick="selectList('Opt3')">Opt3</li>
</ul>
</label>
</div>

Related

How to better write using jquery to click on the periphery to close the pop-up window

I want to realize that a window can appear when the button is clicked, and when the click is outside the window, the window can be closed, but at present I judge it by e.target.className, but I think this way of writing is not ideal because if it is inside the window If there are a lot of objects, I seem to have to write more e.target.className to complete the requirements, so I want to find a better and more professional way of writing!
In addition, I hope that I can click the tool button repeatedly to close and open the window, but I don't know how to write it. I tried to use the toggle method but still can't achieve the effect. I hope I can get your help, thank you.
$(".tool").on("click", function (e) {
$(document).on("click", function (e) {
if (
e.target.className == "delet_wrap" ||
e.target.className == "consider" ||
e.target.className == "confirm" ||
e.target.className == "btn_group" ||
e.target.className == "tool" ||
e.target.className == "txt"
) {
$(".delet_wrap").css("display", "inline-block");
} else {
$(".delet_wrap").css("display", "none");
}
});
});
body {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.tool {
position: relative;
text-decoration: none;
background-color: yellow;
border: 1px solid #222;
border-radius: 6px;
padding: 10px;
color: #222;
}
.tool .delet_wrap {
padding: 20px;
position: absolute;
bottom: -90px;
left: 10px;
background-color: #ccc;
border: 1px solid #222;
display: none;
}
.tool .delet_wrap p {
text-align: center;
}
.tool .delet_wrap .btn_group {
display: flex;
white-space: nowrap;
margin-top: 10px;
}
.tool .delet_wrap .btn_group .consider {
background-color: #fff;
border: 1px solid #222;
margin-right: 10px;
}
.tool .delet_wrap .btn_group .confirm {
text-decoration: none;
border: 1px solid #222;
margin-left: 10px;
background-color: #222;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<a href="javascript:;" class="tool">tool
<div class="delet_wrap">
<p class="txt">Are you sure you want to remove?</p>
<div class="btn_group">
<button class="consider">consider</button>
<button class="confirm">confirm</button>
</div>
</div>
</a>
First of all, you don't need any bloatware-jquery for this, you can use combination of element.matches and element.closest to check clicked element:
document.querySelectorAll(".tool").forEach(el => el.addEventListener("click", function (e) {
let node = e.target;
//check if clicked any buttons inside the popup
if (node.matches("button.consider"))
{
console.log(node.textContent);
return;
}
if (node.matches("button.confirm"))
{
console.log(node.textContent);
return;
}
//if popup element element exists as a child, the main button clicked
if (node = node.querySelector(".delet_wrap"))
{
//if popup already opened do nothing
if(node.style.display == "inline-block")
return;
document.body.click(); //close other popups
//show popup
node.style.display = "inline-block";
//create onClick function so we can remove listener later
const onClick = e =>
{
//hide popup
node.style.display = "none";
//remove listener
document.removeEventListener("click", onClick);
};
//add listener
document.addEventListener("click", onClick);
}
// stop propagation to prevent trigger document click
e.stopPropagation();
}));
body {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.tool {
position: relative;
text-decoration: none;
background-color: yellow;
border: 1px solid #222;
border-radius: 6px;
padding: 10px;
color: #222;
}
.tool .delet_wrap {
padding: 20px;
position: absolute;
bottom: -90px;
left: 10px;
background-color: #ccc;
border: 1px solid #222;
display: none;
}
.tool .delet_wrap p {
text-align: center;
}
.tool .delet_wrap .btn_group {
display: flex;
white-space: nowrap;
margin-top: 10px;
}
.tool .delet_wrap .btn_group .consider {
background-color: #fff;
border: 1px solid #222;
margin-right: 10px;
}
.tool .delet_wrap .btn_group .confirm {
text-decoration: none;
border: 1px solid #222;
margin-left: 10px;
background-color: #222;
color: #fff;
}
<a href="javascript:;" class="tool">tool
<div class="delet_wrap">
<p class="txt">Are you sure you want to remove?</p>
<div class="btn_group">
<button class="consider">consider</button>
<button class="confirm">confirm</button>
</div>
</div>
</a>
<a href="javascript:;" class="tool">tool 2
<div class="delet_wrap">
<p class="txt">Are you sure you want to remove? 2</p>
<div class="btn_group">
<button class="consider">consider 2</button>
<button class="confirm">confirm 2</button>
</div>
</div>
</a>
You can use an array to hold these, then use includes to determine:
const className = ['delet_wrap', 'consider', 'confirm', 'btn_group', 'tool', 'txt']
$(".tool").on("click", function (e) {
$(document).on("click", function (e) {
if (className.includes(e.target.className)) {
$(".delet_wrap").css("display", "inline-block");
} else {
$(".delet_wrap").css("display", "none");
}
});
});

Both parent and child links open

I have a parent <a> with an href attribute. I have a child <p> and I want a small box to be opened when I click on child element.
The problem is when I click on the child element, it opens the small box but after a second the parent link opens up too. I don't want the parent link to be opened when I click on child element. I added event.stopPropagation() but it doesn't change anything. I also added z-index property but no changes either.
In my JS Fiddle demo you can see a live example; but here is my code so far:
.parent {
background-color: blue;
display: inline-block;
width: 400px;
height: 300px;
z-index: 1;
}
.child {
color: black;
background-color: yellow;
display: inline-block;
width: 100px;
height: 50px;
z-index: 2;
}
[title] {
position: relative;
display: inline-flex;
justify-content: center;
}
.child:focus::after {
content: attr(title);
position: absolute;
top: 90%;
color: #000;
background-color: #fff;
border: 1px solid;
width: fit-content;
padding: 3px;
font-size: 10px;
z-index: 20;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<a class="parent" href="https://www.google.com/">
<p class="child" title="This is mobile tooltip" tabindex="0" (click)="$event.stopPropagation();">Click</p>
</a>
JS Fiddle demo
PS: I cannot use jQuery.
Thanks.
Let me explain What I did was create a variable that changed whenever it hovered on the element or off the element. Next Whenever the user clicked on it I just ran a check to see if the mouse was not hovering on the element and executed a code if was on the element I ran a other code
var mouse = false;
function mouseStatus(n) {
mouse = n;
}
function parent() {
if (mouse == false) {
console.log('parent');
window.open('www.google.com');
}
}
function child() {
console.log('child');
}
.parent {
background-color: blue;
display: inline-block;
width: 400px;
height: 300px;
z-index: 1;
}
.child {
color: black;
background-color: yellow;
display: inline-block;
width: 100px;
height: 50px;
z-index: 2;
}
[title] {
position: relative;
display: inline-flex;
justify-content: center;
}
.child:focus::after {
content: attr(title);
position: absolute;
top: 90%;
color: #000;
background-color: #fff;
border: 1px solid;
width: fit-content;
padding: 3px;
font-size: 10px;
z-index: 20;
}
<a class="parent" onclick="parent()">
<p class="child" title="This is mobile tooltip" onmouseover="mouseStatus(true);" onmouseout="mouseStatus(false);" onclick="child()">Click</p>
</a>
call a function instead :
onEvent(event) {
event.stopPropagation();
return false;
}
calling the function with
(click) ="onEvent($event)"

Issue with floating buttons right of my to do list

I'm trying to create a to-do list app with jquery or bootstrap. Just css grid and vanilla js.
I'm currently having an issue floating my 'delete' buttons of list items to the right. The first one floats all the way right white the others kind of stagger.
Below is my code:
var addItemButton = document.getElementById('addItem')
var onEnter = document.getElementById('newNote')
//below event listener adds an item to the list on click
addItemButton.addEventListener('click', function() {
let item = document.getElementById('newNote').value
let node = document.createElement("LI")
let textnode = document.createTextNode(item)
node.appendChild(textnode)
if (item) {
document.getElementById('list-body').appendChild(node)
}
let node2 = document.createElement('BUTTON')
let textnode2 = document.createTextNode('Delete')
node2.appendChild(textnode2)
node.appendChild(node2)
node2.addEventListener('click', function() {
node2.parentNode.parentNode.removeChild(node)
});
document.getElementById('newNote').value = ''
});
onEnter.addEventListener('keyup', function(event) {
if (event.keyCode === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
addItemButton.click();
}
})
function applyButton() { //onload for dummy data or data from db
let getListObjects = document.querySelectorAll("li")
for (let i = 0; i < getListObjects.length; i++) {
let node2 = document.createElement('BUTTON')
let textnode2 = document.createTextNode('Delete')
node2.appendChild(textnode2)
getListObjects[i].appendChild(node2)
let y = getListObjects[i].querySelector('button')
y.addEventListener('click', function() {
y.parentNode.parentNode.removeChild(getListObjects[i])
});
}
}
.container {
display: grid;
height: 100%;
grid-gap: 5px;
grid-template-columns: repeat(3, 1fr)
}
.container2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
background-color: grey;
border: 1px solid grey;
}
#main-grid {
grid-column: 2;
}
#newNote {
height: 25px;
}
#inputIdForGrid {
justify-content: left;
align-items: center;
display: flex;
padding-left: 0.3em;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
button {
margin: 10px;
padding: 10px 10px;
background-color: green;
border: none;
color: white;
font-size: 14px;
}
#addItem {
margin-left: 1em;
padding: 0.5em;
color: white;
font-size: 1.5em;
float: right;
}
ul {
list-style-type: none;
padding: 0px;
margin: 0px;
}
li {
height: 50px;
line-height: 50px;
}
li:nth-child(2n+0) {
background-color: grey;
}
li>button {
background-color: red;
}
h1 {
text-align: center
}
<body onload="applyButton()">
<h1>Vanilla JS ToDo List - No Jquery, No Bootstrap</h1>
<div class='container'>
<div id='main-grid'>
<div class="container2">
<div id='inputIdForGrid'><input type='text' placeholder="Enter
List Items Here" id='newNote'> </div>
<div> <a href='#'><i class="fas fa-plus-circle" id='addItem'
type='submit' ></i></a></div>
</div>
<ul id='list-body'>
<li>Walk Dog</li>
<li>Buy Apples</li>
<li>Hit Gym and Lift Bro</li>
<li>Stretch</li>
</ul>
</div>
</div>
</body>
I really am unsure how to solve this issue currently, I've never faced this problem before.
Have you tried:
button {
float:right;
}
I think the problem has something to do with the button margin.
The margin (10px) is bigger than the row so it will push the bottom buttons to the left.
Just change the margin: 10px; to margin: 7px 10px 7px 10px;
button {
margin: 7px 10px 7px 10px;
padding: 10px 10px;
background-color: green;
border: none;
color: white;
font-size: 14px;
float: right;
}
Use flexbox on parent element and justify-content: flex-end.

`transitionend` stops firing after several events

I have a tricky problem. When a user presses on a div I want to add a css class to that div with name .active, using css transition. Then, after a short timeout, I want to handle transitionend event where I will remove the class .active from the div. The problem is, if a user presses on the button too fast, say 15-20 times per second, transitionend eventually stops firing.
You can see this effect here(clickable link), open Chrome browser(I wasn't able to reproduce it in FF) and start clicking on the button as fast as you can. After 15-20 clicks transitionend will stop triggering.
I think, when transitionend handler is still working, a user can press the button once again and the div will get .active class, but it will not trigger transition event. The question is - is it possible to write bullet-proof code to clear the .active class using transitionend event only ?
Thank you.
*edit* embedded snippet below
var blk = document.querySelector('.animated-element');
var btn = document.querySelector('.button');
var ctr = document.querySelector('.click-counter');
var lgr = document.querySelector('.logger');
btn.addEventListener('click', function() {
var currentValue = Number(ctr.innerHTML);
ctr.innerHTML = ++currentValue;
if (!blk.classList.contains('active')) {
blk.classList.add('active');
}
});
blk.addEventListener('transitionend', function() {
blk.classList.remove('active');
var li = document.createElement('li');
li.innerHTML = 'Transition end';
lgr.appendChild(li);
});
.scene {
position: relative;
width: 300px;
height: 300px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.animated-element {
width: 200px;
height: 200px;
margin-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 2em;
background-color: #ff0000;
color: white;
border-radius: 5px;
transition: all 500ms;
}
.animated-element.active {
font-weight: 700;
/* background-color: #ff0055; */
}
.button {
padding: 5px 10px;
border: 2px solid black;
border-radius: 5px;
text-align: center;
cursor: pointer;
user-select: none;
}
.click-counter {
position: absolute;
top: 0;
left: 0;
min-height: 17px;
min-width: 17px;
padding: 2px 4px;
color: gray;
font-weight: bold;
text-align: center;
border: 2px solid gray;
border-radius: 50%;
user-select: none;
}
.logger {
width: 300px;
padding: 0;
list-style: none;
text-align: center;
}
<body>
<main>
<div class="scene">
<div class="click-counter">0</div>
<div class="animated-element">
<span>ABC</span>
</div>
<div class="button">
Press me several times
</div>
</div>
<ul class="logger"></ul>
</main>
</body>

Swappable CSS theme with jQuery event handler

I'm trying to create a simple swappale CSS theme with a jQuery event handler.
As you can see, I was able to modify .outer but I don't know how to style the spans. The default theme doesn't need further customization, but I would like to change the color/size of the spans and apply a hover effect, etc. for 'mytheme'.
The HTML shouldn't be changed but the JS can be edited.
Any input is highly appreciated. How can I use [data-theme="mytheme"] with other elements?
Here's the JSFiddle
This is the HTML:
<div class="outer" data-theme="default">
<ul class="list">
<li><span>1</span></li>
<li><span>2</span></li>
<li><span>3</span></li>
<li><span>4</span></li>
<li><span>5</span></li>
<li><span>6</span></li>
<li><span>7</span></li>
<li><span>8</span></li>
<li><span>9</span></li>
<li><span>0</span></li>
</ul>
<input type="button" value="Swap Theme" id="btnSwapTheme" />
</div>
JS:
$(document).ready(function() {
//Swap Theme event handler
$('#btnSwapTheme').on('click', function(evt) {
var theme = $('div.outer').attr('data-theme');
if (theme == 'default') {
$('div.outer').attr('data-theme', 'mytheme');
} else {
$('div.outer').attr('data-theme', 'default');
}
});
});
CSS:
.outer {
text-align: center;
}
.outer input {
margin: 0.5em;
}
.outer[data-theme="default"] {
background-color: #c0c0c0;
}
.outer[data-theme="mytheme"] {
background-color: red;
}
ul.list {
width: 13em;
margin: 0;
margin-left: auto;
margin-right: auto;
text-align: center;
padding: 0;
list-style-type: none;
text-align: center;
}
ul.list li {
display: inline-block;
font-weight: bold;
border: 1px solid blue;
margin: .5em;
}
ul.list li span {
display: inline-block;
height: 3em;
width: 3em;
line-height: 3em;
cursor: pointer;
}
For example like this:
.outer[data-theme="mytheme"] span {
background-color: green;
}
.outer[data-theme="mytheme"] span:hover {
background-color: coral;
}
Demo: http://jsfiddle.net/6kyhbhus/1/
You just need to prefix any selector with .outer[data-theme="mytheme"] since this is wrapping container for your elements.

Categories

Resources