I'm trying to trigger a dropdown with pure javascript that would have the following functionalities:
Clicking anyhwere outside the dropdown will close the dropdown
The dropdown can be toggled.
When clicking a link inside the dropdown, the ability to control
whether the menu should close or stay open
Now I have an issue. If I try to accomplish 1 (above) I am unable to accomplish 2 (toggle the dropdown) and vice-versa.
Regarding the 3 (above), the fiddle will clearly show you that when clicking a link on the first dropdown, the dropdown doesn't disappear. But when clicking a link from the second dropdown makes the dropdown disappear. How do I control this?
The Fiddle: https://jsfiddle.net/TheEarlyMan/0u6nnakm/
My JS:
class dropdown {
constructor() {
this.trigger = document.querySelectorAll('.dropdown [data-trigger]');
this.target = document.querySelectorAll('.dropdown [data-dropdown]');
this.init();
}
init() {
for (let i = 0; i < this.trigger.length; i++) {
this.handleClick(this.trigger[i]);
}
}
handleClick(el) {
el.addEventListener('click', (event) => {
event.preventDefault();
el.nextElementSibling.classList.toggle('active');
});
for (let i = 0; i < this.target.length; i++) {
this.handleClose(this.target[i]);
}
}
handleClose(el) {
window.addEventListener('mouseup', (event) => {
if (event.target != el && event.target.parentNode != el) {
if (el.classList.contains('active')) {
el.classList.remove('active');
}
}
});
}
}
window.addEventListener('load', () => {
new dropdown();
}, false)
The CSS:
.dropdown {
position: relative;
}
.dropdown-content {
position: absolute;
display: none;
z-index: 1;
min-width: 10vr;
padding: 0;
lost-column: 1/3;
padding: calc(1vr - 1px);
background: #fff;
border: 1px solid #ccc;
}
.dropdown-menu {
display: none;
position: absolute;
z-index: 1;
background: #fff;
border: 1px solid #ccc;
width: 8vr;
li {
margin: 0;
display: block;
}
a {
padding: 0.5vr 0.5vr;
&:hover {
background: #eee;
}
}
}
.dropdown-menu, .dropdown-content {
&.active {
display: block;
}
}
Related
I am working on range slider for my website. I am stuck in to make it possible that on clicking specific box all previous steps gets applied with class ".previous" and all steps next to current one get applied with ".next" class, which have different styling for previous steps and next steps, according to the data-label value of that specific div. Lets take an example for a simple scenario if I click on step 7 then step 5 and 6 gets red, and step 8 and 9 gets grey, if I click on step 9 all previous gets applied with ".previous" class. In simple words all steps having number greater than current active step get applied with ".next" class and all those having number less then current active gets applied with ".previous" class .Attached is the source code, Any suggestion or help would be appreciated.
const rangeSlider = document.querySelector('#price_slider');
rangeSlider.addEventListener("input", rangeScript);
const customProgress = document.querySelector('#customProgress');
for (let i = 0; i < rangeSlider.max - rangeSlider.min; i++) {
const step = document.createElement('div');
step.classList.add('step');
step.setAttribute('data-label', +rangeSlider.min + i + 1);
step.addEventListener('click', (e) => {
let val = e.target.dataset.label;
document.querySelector('#price_slider').value = val
rangeScript({
target: rangeSlider
})
})
customProgress.appendChild(step);
}
customProgress.querySelector(`.step[data-label="${rangeSlider.value}"]`)
.classList.add('current')
function rangeScript(e) {
const target = document.getElementById('progress');
let newValue = parseInt(e.target.value);
const currentStep = customProgress.querySelector(`.step.current`);
if (currentStep) {
currentStep.classList.remove('current');
}
nextStep = customProgress.querySelector(`.step[data-label="${newValue}"]`);
if (nextStep) {
nextStep.classList.add('current')
}
}
#customProgress {
display: flex;
width: 100%;
height: 25px;
margin-top: 44px;
}
.step {
position: relative;
background: #f5f5f5;
height: 40px;
width: 100%;
border-radius: 4px 0 0 4px;
border-right: 1px solid white;
}
.step::after {
content: attr(data-label);
position: absolute;
top: -1.5em;
right: -0.25em;
}
.step ~ .current,
.step.current {
background: #a6983e;
}
.previous {
background: red;
}
.next {
background: #f5f5f5;
}
<div id="customProgress"></div>
<div id="progress" style="width: 100%">
<input id="price_slider" type="range" min="4" max="9" value="" style="display:none" />
</div>
You can do something like this
const rangeSlider = document.querySelector('#price_slider');
rangeSlider.addEventListener("input", rangeScript);
const customProgress = document.querySelector('#customProgress');
for (let i = 0; i < rangeSlider.max - rangeSlider.min; i++) {
const step = document.createElement('div');
step.classList.add('step');
step.setAttribute('data-label', +rangeSlider.min + i + 1);
step.addEventListener('click', (e) => {
let val = e.target.dataset.label;
document.querySelector('#price_slider').value = val
rangeScript({
target: rangeSlider
})
})
customProgress.appendChild(step);
}
customProgress.querySelector(`.step[data-label="${rangeSlider.value}"]`)
.classList.add('current')
function rangeScript(e) {
const target = document.getElementById('progress');
let newValue = parseInt(e.target.value);
const currentStep = customProgress.querySelector(`.step.current`);
if (currentStep) {
currentStep.classList.remove('current');
}
nextStep = customProgress.querySelector(`.step[data-label="${newValue}"]`);
if (nextStep) {
nextStep.classList.add('current')
}
}
#customProgress {
display: flex;
width: 100%;
height: 25px;
margin-top: 44px;
}
.step {
position: relative;
background: #f5f5f5;
height: 40px;
width: 100%;
border-radius: 4px 0 0 4px;
border-right: 1px solid white;
}
.step::after {
content: attr(data-label);
position: absolute;
top: -1.5em;
right: -0.25em;
}
.step ~ .current,
.step.current {
background: #a6983e;
}
.step {
background: green;
}
.current ~ div {
background: red;
}
<div id="customProgress"></div>
<div id="progress" style="width: 100%">
<input id="price_slider" type="range" min="4" max="9" value="" style="display:none" />
</div>
I have a button enclosing an icon. I believe the icon is interfering with my click event. I am only able to click on the icon margins to activate the onclick event, but nothing happens when I click on the icon. I replaced the icon with some text and the button onclick works perfectly fine. I have tried z-index to put the icon behind the button, but to no avail. Can someone explain why the icon blocks the click from occurring and how I can fix it?
html:
<div class="navMenu">
<button onclick="navClick()" class="navMenu-button"><i class="fas fa-bars"></i></button>
<div id="navList" class="navMenu-content">
Home
About
Resume
</div>
sass:
.navMenu{
position: relative;
display: inline-block;
margin-top:5px;
margin-left:5px;
&-button {
background-color: #615b5b;
border-radius:50%;
color: white;
padding: 7px;
opacity:0.7;
border:none;
font-size: 14px;
cursor: pointer;
}
&-button:hover, &-button:focus {
background-color: #615b5b;
}
&-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 200px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
&-content .navMenu-link{
color: $body-text-color;
padding: 12px 16px;
text-decoration: none;
display: block;
}
&-content .navMenu-link:hover {
background-color: #ddd;
}
&-show {
display:block;
}
}
js:
function navClick() {
document.getElementById("navList").classList.toggle("navMenu-show");
}
window.onclick = function(event) {
if (!event.target.matches('.navMenu-button')) {
var dropdowns = document.getElementsByClassName("navMenu-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var show = dropdowns[i];
if (show.classList.contains('navMenu-show')) {
show.classList.remove('navMenu-show');
}
}
}
}
This is happening becuase your are setting an event that verifies if the element clicked contains an especific class, and indeed when it clicks the icon, it won't match because the icon does not contains the class you can solve it asking if also the parent contains the class....
window.onclick = function(event) {
if (event.target.matches('.navMenu-button') ||
event.target.parentNode.matches('.navMenu-button')
) {
console.log("it matches...");
}
}
.icon {
background:red;
}
<button class="navMenu-button">this is the button
<div class="icon">this is the icon</div>
</button>
On the other hand you could reference the click event using the "onclick" method in this case it will solve it automatically..
var button = document.querySelectorAll('.navMenu-button')[0];
button.onclick = function() {
console.log("button clicked");
}
I would like to have a button, that when clicked shows a pre-loading gif for 1-second, and then closes the div #sidefilter, that the user has open.
I have been able to get both of them working independently, but they are not working as intended, I think I need to add a delay to #dismissiefilter, and then end the loading GIF when executed.
What needs to be added to enable the above to happen?
$('.showloader').button();
$('.showloader').click(function() {
$(this).html('<img src="http://www.bba-reman.com/images/fbloader.gif" />');
});
$('#dismisssidefilter, .overlay').on('click', function() {
$('#sidefilter').removeClass('active');
$('.overlay').removeClass('active');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
See results
Working link:
https://clearing.co.uk/dev/universities/
Filter button next to 'search for a university' input field
I've updated my answer with a demo representing your site
$(function() {
var devActions = function(str, clear) {
let output = $('#dev-output');
if (typeof clear === 'boolean' && clear === true) {
output.empty();
}
output.append(' - ' + str + '<br />');
console.log('devActions:', str);
}
$('#sidefilterCollapse').on('click', function() {
var sidebar = $('#sidefilter');
if (!sidebar.hasClass('active')) {
sidebar.addClass('active');
devActions('#sidefilterCollapse clicked', true);
devActions('Adding .active class to #sidefilter');
if ($('#sidefilter').hasClass('active')) {
devActions('#sidefilter now has .active class');
} else {
devActions('somethings went wrong #sidefilter doesn\'t have .active class');
}
} else {
devActions('#sidefilter already has .active class');
}
})
$('.showloader').on('click', function(e) {
e.preventDefault();
var btn = $(this);
// add .loading class so we can prevent spam click
if (!btn.hasClass('loading')) {
btn.addClass('loading');
var saveOriginalText = $(this).html();
var duration = 3000;
btn.html('<img src="http://www.bba-reman.com/images/fbloader.gif" />');
if (btn.children('img').length) {
devActions('.showloader clicked and image was added.');
}
btn.stop(true).animate({
opacity: 1
}, {
duration: duration,
start: function() {
devActions('animation started');
},
complete: function() {
devActions('animation done');
// remove .loading class so we can make the button available again
btn.removeClass('loading');
btn.html(saveOriginalText);
if ($(this).html() == saveOriginalText) {
devActions('original button content was added on .showloader');
}
// do custom stuff after 1000ms
$('#sidefilter').removeClass('active');
if (!$('#sidefilter').hasClass('active')) {
devActions('.active class has been removed from #sidefilter');
}
}
})
} else {
devActions('---- don\'t spam click, currently loading ---');
}
});
})
body {
overflow-x: hidden;
font-family: "Segoe UI";
}
#sidefilterCollapse,
.showloader {
padding: 3px 5px;
line-height: 1rem;
width: 100px;
background: red;
color: white;
display: inline-block;
text-align: center;
border-radius: 20px;
cursor: pointer;
text-decoration: none;
}
#sidefilter {
text-align: center;
position: fixed;
right: -200px;
top: 0;
padding: 15px 10px;
box-sizing: border-box;
width: 200px;
height: 100%;
border-left: 1px solid lightgrey;
box-shadow: -2px 0 13px 2px grey;
transition: right 0.2s ease-in-out;
}
#sidefilter.active {
right: 0;
transition: right 0.2s ease-in-out;
}
#sidefilter p {
text-align: left;
margin-bottom: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="sidefilterCollapse">
Filter Button
</div>
<div id="sidefilter">
<p>
text text text text text text text text text
</p>
See results
</div>
<div id="dev-output"></div>
I have this code:
Full Code:
https://codepen.io/anon/pen/wmLBxW
JavaScript code:
function HideOnClickOutside(aContentElement, aFullScreenElement) {
const outsideClickListener = event => {
event.preventDefault();
let isClickInside = aContentElement.contains(event.target);
if (!isClickInside) {
aFullScreenElement.classList.toggle("hidden", true);
removeClickListener();
}
};
const removeClickListener = () => {
document.removeEventListener("click", outsideClickListener);
};
document.addEventListener("click", outsideClickListener);
}
CSS
#fullscreen {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow: auto;
background: black;
opacity: 0.5;
}
.hidden {
display: none;
}
#journal-show-entry {
display: flex;
justify-content: center;
width: 40%;
height: 60%;
border: 1px solid #9b6400;
background-color: #ffffff;
}
HTML
Link
<div id="fullscreen" class="hidden">
<div id="journal-show-entry">
</div>
</div>
Which I found in this thread:
How do I detect a click outside an element?
However just like in my CodePen, it triggers the outsideClickListener on the same click that adds the EventListener making the div hide itself again on the same click and therefore never shows.
Why is this happening?
What is happening is that the event is propagating onto the next layer (the hidden div)
Adding this will fix your problem:
function ShowFullScreenDiv(event) {
event.stopPropagation(); // <-- add this
divFullScreen.classList.toggle("hidden", false);
HideOnClickOutside(divEntry, divFullScreen);
}
And ofcourse add the event to the html:
Link
I'm creating an off-canvas nav that closes on three conditions:
The user toggles the nav button
the user clicks a link inside the nav
the user clicks anywhere but the nav when the nav is open.
I've got the first two conditions working but not the third. Below is my code. What I'm trying to accomplish is essentially the following:
check for a click on the body and if that click is the (in this case) .pageContainer run a second check to see if the nav has the class "showMenu" and the flag is == true
$(document).ready(function() {
var button = $('.button');
var ocn = $('.ocn');
var test = $('.test');
var flag = false;
//toggle menu using just the button
button.click(function() {
if ( flag == false ) {
ocn.addClass('showMenu');
flag = true;
} else {
ocn.removeClass('showMenu');
flag = false;
}
});
//close the menu clicking on a link
test.click(function() {
ocn.removeClass('showMenu');
flag = false;
});
//close menu when click off canvas
/*
$('body').on('click', '.pageContainer', function(e) {
if( ocn.hasClass('showMenu') && flag == true) {
ocn.removeClass('showMenu');
flag = false;
}
});
*/
});
* {
padding: 0;
margin: 0;
}
.ocn {
position: absolute;
left: -300px;
top: 0;
width: 300px;
height: 100vh;
background-color: #ccc;
transition: left .2s ease;
z-index: 2;
border: 1px solid;
}
.showMenu {
left: 0px;
}
.pageContainer {
height: 500px;
width: 1000px;
border: 1px solid;
display: block;
margin: 0 auto;
}
.button {
height: 40px;
width: 40px;
border: 1px dashed;
display: block;
position: relative;
left: 400px;
cursor: pointer;
}
.test {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="ocn">
<p class="test">here is some text for the menu</p>
</div>
<div class="pageContainer">
<div class="button"></div>
</div>
You can use the e.target property to compare what was clicked. Similar to this:
if(e.target == pageContainer[0])
You can now apply the required logic when .pageContainer was clicked.
$(document).ready(function() {
var pageContainer = $('.pageContainer');
var button = $('.button');
var ocn = $('.ocn');
var test = $('.test');
var flag = false;
//toggle menu using just the button
button.click(function() {
if (flag == false) {
ocn.addClass('showMenu');
flag = true;
} else {
ocn.removeClass('showMenu');
flag = false;
}
});
//close the menu clicking on a link
test.click(function() {
ocn.removeClass('showMenu');
flag = false;
});
//close menu when click off canvas
$('body').on('click', '.pageContainer', function(e) {
if (e.target == pageContainer[0]) {
//console.log('pageContainer was clicked')
if (ocn.hasClass('showMenu') && flag == true) {
ocn.removeClass('showMenu');
flag = false;
}
};
});
});
* {
padding: 0;
margin: 0;
}
.ocn {
position: absolute;
left: -300px;
top: 0;
width: 300px;
height: 100vh;
background-color: #ccc;
transition: left .2s ease;
z-index: 2;
border: 1px solid;
}
.showMenu {
left: 0px;
}
.pageContainer {
height: 500px;
width: 1000px;
border: 1px solid;
display: block;
margin: 0 auto;
}
.button {
height: 40px;
width: 40px;
border: 1px dashed;
display: block;
position: relative;
left: 400px;
cursor: pointer;
}
.test {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="ocn">
<p class="test">here is some text for the menu</p>
</div>
<div class="pageContainer">
<div class="button"></div>
</div>