I want to make a "popup" box activated by a button that reduces the opacity of all other elements. When the user clicks out of the box, it should disappear and the opacity should go back to normal. However, these two functions are conflicting with each other. It requires me to click the button TWICE in order for showBox() to be called. And clicking out of the box does nothing unless I reinvoke hideOnClickOutside(document.querySelector('div')); in the browser's console.
Why do I have to click "New Audio" twice and why does hideOnClickOutside() not work unless reinvoked?
function showBox() {
document.body.style.opacity = "0.5";
document.querySelector('div').style.display = "block";
}
document.querySelector('button').addEventListener('click', showBox);
const isVisible = elem => !!elem && !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
function hideOnClickOutside(element) {
const outsideClickListener = event => {
if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
element.style.display = 'none';
removeClickListener()
document.body.style.opacity = "1";
}
}
const removeClickListener = () => {
document.removeEventListener('click', outsideClickListener)
}
document.addEventListener('click', outsideClickListener)
}
hideOnClickOutside(document.querySelector('div'));
<button>New Audio</button>
<div style="display: none">
<button>Record Directly</button>
</div>
hideOnClickOutside() function was taken from another StackOverflow answer
Edit
I figured out that it requires two clicks because on the first click, showBox() is called, but immediately after, so is outsideClickListener, and at this point the element is NOW visible AND the user has clicked "outside" the element. This reverts the style changes of showBox().
The easiest fix is to store the reference to the "New Audio" button and check to see if that is the target of the click on document. If so, then return from the function without updating DOM.
const button = document.querySelector('button')
button.addEventListener('click', showBox);
// ..
function hideOnClickOutside(element) {
const outsideClickListener = event => {
if (event.target === button) return
// ..
Keep in mind, with the current code you have, the hideOnClickOutside function only gets until the first time isVisible is true and the target is not button, since you remove the event listener on that condition.
function showBox(e) {
document.body.style.opacity = "0.5";
document.querySelector('div').style.display = "block";
}
const button = document.querySelector('button')
button.addEventListener('click', showBox);
const isVisible = elem => !!elem && !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
function hideOnClickOutside(element) {
const outsideClickListener = event => {
if (event.target === button) return
if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
element.style.display = 'none';
removeClickListener()
document.body.style.opacity = "1";
}
}
const removeClickListener = () => {
document.removeEventListener('click', outsideClickListener)
}
document.addEventListener('click', outsideClickListener)
}
hideOnClickOutside(document.querySelector('div'));
<button>New Audio</button>
<div style="display: none">
<button>Record Directly</button>
</div>
The other problem is that once the showBox function is called, you actually probably want the button to be considered outside. Let's refactor your code to store references to the showButton and box, add a flag to disable the showButton and only add the event listener to the document if the showButton is clicked and remove the event listener only when the box is displayed.
You can later refactor this to fit your particular use case. The idea is to think of the various states this application can be in and create functions to manage that state.
const box = document.querySelector('#box');
const showButton = document.querySelector('#show-button');
showButton.addEventListener('click', showBox);
let isDisabled = false;
const isVisible = elem => !!elem && !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js
function toggleDisabled(bool) {
showButton.attributes.disabled = bool;
isDisabled = bool;
}
function toggleDisplay(display, opacity) {
document.body.style.opacity = opacity;
box.style.display = display;
}
function showBox(event) {
if (!isDisabled) {
event.preventDefault();
event.stopPropagation();
toggleDisplay("block", 0.5);
toggleDisabled(true);
document.addEventListener('click', outsideClickListener);
}
}
function outsideClickListener(event) {
if (!box.contains(event.target) && isVisible(box)) { // or use: event.target.closest(selector) === null
toggleDisplay("none", 1);
toggleDisabled(false);
document.removeEventListener('click', outsideClickListener)
}
}
<button id="show-button">New Audio</button>
<div id="box" style="display: none">
<button>Record Directly</button>
</div>
Related
I have this big class Search, which controls my search bar on my website. Now, when a input is focused, i dont want my s key (which pops out the search bar) to execute when a input is focused. I tried with document.activeElement, but then, the search bar wont even open, whilst the input not being focused. You can see it, under keydown event listener, under Events comment
class Search {
// Describe and create object
constructor() {
this.openButton = document.querySelectorAll('.js-search-trigger');
this.closeButton = document.querySelector('#close-button');
this.searchOverlay = document.querySelector('.search-overlay');
this.searchField = document.getElementById('search-term');
this.typingTimer;
this.events();
this.isSpinnerVisible = false;
this.resultsDiv = document.getElementById('search-overlay__results');
this.previousValue;
console.log(this.openButton);
}
// Events
events() {
this.openButton.forEach(e => {
e.addEventListener('click', () => {
this.openOverlay();
document.body.classList.add('body-no-scroll');
});
})
this.closeButton.addEventListener('click', () => {
this.closeOverlay();
document.body.classList.remove('body-no-scroll');
})
document.addEventListener('keydown', (e) => {
if(e.key === 's' && !(this === document.activeElement)){
this.openOverlay();
document.body.classList.add('body-no-scroll');
console.log("s pressed")
}
if(e.key === 'Escape' && this.isOverlayOpen){
this.closeOverlay();
document.body.classList.remove('body-no-scroll');
console.log("esc pressed");
}
});
this.searchField.addEventListener('keyup', () => {
this.typingLogic();
})
}
// Methods
openOverlay(){
this.searchOverlay.classList.add('search-overlay--active');
this.isOverlayOpen = true;
}
closeOverlay(){
this.searchOverlay.classList.remove('search-overlay--active');
}
typingLogic(){
if(this.searchField.value != this.previousValue){
clearTimeout(this.typingTimer);
if(this.searchField.value){
if(!this.isSpinnerVisible){
this.resultsDiv.innerHTML = '<div class="spinner-loader"></div>';
this.isSpinnerVisible = true;
}
this.typingTimer = setTimeout(this.getResults(),2000);
}else{
this.resultsDiv.innerHTML = '';
this.isSpinnerVisible = false;
}
}
this.previousValue = this.searchField.value;
}
getResults(){
this.typingTimer = setTimeout(()=> {
this.resultsDiv.innerHTML = 'Some here';
this.isSpinnerVisible =false;
},2000)
}
}
export default Search
You can check tagName property of activeElement. And if it is not input then proceed with your code. Update your condition like below.
if(e.key === 's' && document.activeElement.tagName.toLowerCase() != 'input')
I'm trying to hide the "modal" box when the user press Esc key.
So, I first check where the box contains class - 'hidden', which
technically hide the box in UI.
Then if it's not hidden (the box does not contain class - 'hidden') and
appearing on screen, the function will wait for the Esc key for the
box to be disappeared.
Showing and hiding the box parts working just fine, but document.addEventListener part is not working.
const btnopenModal = document.querySelectorAll('.show-modal');
const btnCloseModal = document.querySelector('.close');
const overlay = document.querySelector('.overlay');
const modal =document.querySelector('.modal');
const showModal = function() {
modal.classList.remove('hidden');
overlay.classList.remove('hidden');
};
const hideModal = function() {
modal.classList.add('hidden');
overlay.classList.add('hidden');
}
for(let i = 0; i < btnopenModal.length; i++)
btnopenModal[i].addEventListener('click', showModal);
btnCloseModal.addEventListener('click', hideModal);
overlay.addEventListener('click', hideModal);
if(!overlay.classList.contains('hidden')) {
document.addEventListener('keypress', function(e) {
console.log(e.key);
if(e.key === 'Escape') {
hideModal();
}
})
};
Any other way around for this to work?
I would think that your if statement is evaluated when the webpage first runs, and my guess is that the if statement evaluates to false as it probably does contain the class "hidden" at first. I don't understand why you put it the key handler inside of an if statement, if it is for safety you should put it inside your function like so:
document.addEventListener('keypress', function(e) {
if(!overlay.classList.contains('hidden')) {
console.log(e.key);
if(e.key === 'Escape') {
hideModal();
}
};
})
Move if condition into callback. You want to always add keypress listener, just do not execute hideModal() if !overlay.classList.contains('hidden')
const btnopenModal = document.querySelectorAll('.show-modal');
const btnCloseModal = document.querySelector('.close');
const overlay = document.querySelector('.overlay');
const modal =document.querySelector('.modal');
const showModal = function() {
modal.classList.remove('hidden');
overlay.classList.remove('hidden');
};
const hideModal = function() {
modal.classList.add('hidden');
overlay.classList.add('hidden');
}
for(let i = 0; i < btnopenModal.length; i++)
btnopenModal[i].addEventListener('click', showModal);
btnCloseModal.addEventListener('click', hideModal);
overlay.addEventListener('click', hideModal);
document.addEventListener('keypress', function(e) {
console.log(e.key);
if(e.key === 'Escape' && !overlay.classList.contains('hidden')) {
hideModal();
}
});
I have a problem with my "CheckCheck" function. The following part of the code should generate a to-do task. The input tag dynamically created in JS provides an option to set the priority to the given task. There is an option to set the task to "normal" or "priotity". However, the code sets the fisk task to "on" and after continues with the imposed "priority" and "normal" but inversely. How to prevent this from happening?
The code:
let tasklist = [];
function Apply() {
const Tasktask = document.querySelector(".task-form");
const Taskdate = document.querySelector(".date");
const Taskpriority = document.querySelector(".check-box");
function Prevent() {
if (Tasktask.value.length === 0 || Taskdate.value === "") {
alert("Fields cannot be empty!");
} else {
Pushed();
render();
clear();
}
}
Prevent();
function Pushed() {
let newTasks = new Tasks(Tasktask.value, Taskdate.value, Taskpriority.value);
tasklist.push(newTasks);
updateLocalStorage();
}
function render() {
CheckCheck();
insertTd();
}
function CheckCheck() {
if (Taskpriority.checked === true) {
Taskpriority.value = "priority"
} else {
Taskpriority.value = "normal"
}
}
function clear() {
Tasktask.value = "";
Taskdate.value = "";
Taskpriority.checked = false;
}
function insertTd() {
checkLocalStorage();
const parent2 = document.querySelector(".table-body");
parent2.innerHTML = "";
tasklist.forEach((item) => {
const table = `<tr>
<td>${item.task}</td>
<td>${item.date}</td>
<td>${item.priority}</td>
<td><a class="delete">delete</a></td>
</tr>`;
parent2.insertAdjacentHTML("afterbegin", table);
});
}
function deleteItem() {
const Table = document.querySelector("table").addEventListener("click", (e) => {
const currentTarget = e.target.parentNode.parentNode.childNodes[1];
if (e.target.innerHTML == "delete") {
if (confirm(`Are you sure you want to delete ${currentTarget.innerText}?`))
deleteTask(findTask(tasklist, currentTarget.innerText));
}
if (e.target.classList.contains("status-button")) {
findTask(tasklist, currentTarget.innerText);
}
updateLocalStorage();
insertTd();
});
}
deleteItem();
function deleteTask(currentTask) {
tasklist.splice(currentTask, currentTask + 1);
}
function findTask(taskArray, task) {
if (taskArray.length === 0 || taskArray === null) {
return;
}
for (let item of taskArray)
if (item.task === task) {
return taskArray.indexOf(item);
}
}
}
The other thing which is not working as intended is the confirm prompt. The more tasks I add, the more confirm prompts I get. I.e. for 1 task it is only one confirm window, for 3 tasks - 3 windows etc. Why is that?
I also attach below a JSFiddle link how better understanding.
Link
Thanks in advance for answers.
You don't get the state of a checkbox by reading its value but its checked property. Try document.querySelector('.check-box').checked
You keep reusing the same buttons and add an event listener to them each time. Either clone them every time, or add the listener once right after creating them.
Simple illustration of the problems here
document.querySelector('#readstates').addEventListener('click', e => {
e.preventDefault();
const disp = `Checked\n 1: ${chk1.checked}, 2: ${chk2.checked} \n`
+ `Value\n 1: ${chk1.value}, 2: ${chk2.value}`;
alert(disp);
});
const spawnBut = document.createElement('button');
spawnBut.id = 'spawned';
spawnBut.textContent = 'Spawned';
document.querySelector('#spawnDirty').addEventListener('click', e => {
const previous = document.querySelector('form #spawned');
if (previous) previous.remove();
document.querySelector('#spawnHere').append(spawnBut);
spawnBut.addEventListener('click', e => {
e.preventDefault();
alert('click!');
});
});
document.querySelector('#spawnClone').addEventListener('click', e => {
const previous = document.querySelector('form #spawned');
if (previous) previous.remove();
const nSpawnBut = spawnBut.cloneNode(true);
document.querySelector('#spawnHere').append(nSpawnBut);
nSpawnBut.addEventListener('click', e => {
e.preventDefault();
alert('click!');
});
});
<form>
<p class="inputs">
<label for="chk1">chk1:</label> <input type="checkbox" id="chk1" />
<label for="chk2">chk2:</label> <input type="checkbox" id="chk2" value="mycheckedvalue" />
<button id="readstates">Read chks</button>
</p>
<p class="button-spawners">
Try spamming those then click below:
<button type="button" id="spawnDirty"
title="Each time you click this one, the button below is respawned and a new handler is attached">
Spawn button
</button>
<button type="button" id="spawnClone"
title="This one will spawn a clone each time, so the click handler is attached only once">
Spawn button clone
</button>
</p>
<p id="spawnHere">
New button will spawn here
</p>
</form>
I'm trying to delay the load of a pop-up on a grid of images but want to prevent the ability to click on other images when this happens. Howver if I turn off onclick 'item.onclick = false', I don't seem to be able to turn it back on when the pop-up is turned back on? see line 'item.onclick = true'. Have also tried disabled = true/false but to no avail. Any suggestions?
var caseStudies = document.querySelectorAll('.posterImage');
var caseHover = document.querySelectorAll('.caseHover');
var modal = document.querySelectorAll('.modal');
caseStudies.forEach((button, index) => {
if ((isMobile == true) || (isTablet == true)) {
button.onclick = function(event) {
caseStudies.forEach((item) => {
item.onclick = false;
console.log(item);
});
caseHover.forEach((item) => {
item.classList.add('eventsNone');
console.log(item);
});
setTimeout(function(){
console.log("loading");
modal[index].style.display = "block";
// When the user clicks anywhere outside of the modal, close it (needs to live inside the button.onclick)
window.onclick = function(event) {
if (event.target == modal[index]) {
modal.forEach((item) => {
item.style.display = "none";
});
caseStudies.forEach((item) => {
item.onclick = true;
});
}
}
}, 500);
}
}
else
{
button.onclick = function(event) {
console.log("route2");
modal[index].style.display = "block";
caseStudies.forEach((item) => {
item.classList.add('eventsNone')
});
// When the user clicks anywhere outside of the modal, close it (needs to live inside the button.onclick)
window.onclick = function(event) {
if (event.target == modal[index]) {
modal.forEach((item) => {
item.style.display = "none";
});
caseStudies.forEach((item) => {
item.classList.remove('eventsNone')
});
};
};
};
};
});
Use an inline onclick = "function()" to set your onclick.
When disabling your onlick do it with element.onclick = null.
And enable it again with element.onclick = "function()"
Sorry for getting it wrong before I miss read it and thought you were doing it with buttons.
Also here is a duplicate question how to disable or enable all onClick for images on a page
everyone. I got some problems when I wanna accomplish a drop-down box in a HTML website without using select and option elements, instead of using and elements.
The main function is made up by two parts, the first function is when clicked the first elements in the drop-down box, the hidden parts of list shows up and hide clicked again. The second function is when choose the elements in the hidden list, the text of the elements on the list will replace the first element on the drop-down box.
I have accomplished first function using below codes:
// javascript codes
var searchListBtn = document.getElementById("btn_List");
var a_searchListBtn = document.getElementById("btn_List").getElementsByTagName("a");
function show(event) {
let oevent = event || window.event;
if (document.all) {
oevent.cancelBubble = true;
}
else {
oevent.stopPropagation();
}
// click it to show it, click again to hide it and loop
if (searchListBtn.style.display === "none" || searchListBtn.style.display === "") {
searchListBtn.style.display = "block";
}
else {
searchListBtn.style.display = "none";
}
}
document.onclick = function() {
searchListBtn.style.display = "none";
}
searchListBtn.onclick = function (event) {
let oevent = event || window.event;
oevent.stopPropagation();
}
<!-- html codes -->
<html>
<body>
<div>
<div class="ui-search-selected" onclick="show();">A</div>
<div class="ui-search-selected-list" id="btn_List">
B
C
D
</div>
</div>
</body>
</html>
But when I did the second part, my idea was not clear enough to implement that, I searched if I use select>option elements I could use selectedIndex method to find the index of list, but this is a custom drop-down box formed by div>a structure elements.
I tried to console.log(a_searchListBtn) and show an array from the console, and I could use a_searchListBtn[0~3].text to get the value of B/C/D.
I tried to write codes like below:
a_searchListBtn.onclick = function() {
console.log("Clicked.")
}
But nothing in the console, so, is there anyone could apply some help, thx in advance.
Well you're fetching all the a elements using getElementsByTagName("a"). Now you just need to loop through the results and add a click event listener that will take the innerHTML of that a element and put it into the innerHTML of the ui-search-selected div.
You don't need an index. You can access the clicked element's innerHTML using event.target. See it working in this snippet below:
// javascript codes
var searchListBtn = document.getElementById("btn_List");
var uiSearchSelected = document.getElementById("ui-search-selected");
var a_searchListBtn = document.getElementById("btn_List").getElementsByTagName("a");
for (button of a_searchListBtn) {
button.addEventListener("click", replace);
}
function show(event) {
let oevent = event || window.event;
if (document.all) {
oevent.cancelBubble = true;
}
else {
oevent.stopPropagation();
}
// click it to show it, click again to hide it and loop
if (searchListBtn.style.display === "none" || searchListBtn.style.display === "") {
searchListBtn.style.display = "block";
}
else {
searchListBtn.style.display = "none";
}
}
document.onclick = function() {
searchListBtn.style.display = "none";
}
searchListBtn.onclick = function (event) {
let oevent = event || window.event;
oevent.stopPropagation();
}
function replace(event) {
if (!event) return;
uiSearchSelected.innerHTML = event.target.innerHTML
}
<!-- html codes -->
<html>
<body>
<div>
<div id="ui-search-selected" onclick="show();">A</div>
<div class="ui-search-selected-list" id="btn_List">
B
C
D
</div>
</div>
</body>
</html>
I have tried to use this codes to implement this funtion, it works.
// javascript
var par_searchListBtn = document.getElementById("btn_list_parent");
var searchListBtn = document.getElementById("btn_List");
var a_searchListBtn = document.getElementById("btn_List").getElementsByTagName("a");
// console.log(a_searchListBtn.length);
// console.log(a_searchListBtn);
function show(event) {
let oevent = event || window.event;
if (document.all) {
oevent.cancelBubble = true;
}
else {
oevent.stopPropagation();
}
if (searchListBtn.style.display === "none" || searchListBtn.style.display === "") {
searchListBtn.style.display = "block";
}
else {
searchListBtn.style.display = "none";
}
}
document.onclick = function() {
searchListBtn.style.display = "none";
}
searchListBtn.onclick = function (event) {
let oevent = event || window.event;
oevent.stopPropagation();
}
for(var i = 0; i < a_searchListBtn.length; i++){
a_searchListBtn[i].onclick = function () {
par_searchListBtn.innerHTML = this.innerText;
//searchListBtn.style.display = "none";
}
}
<!-- html codes -->
<html>
<body>
<div>
<div class="ui-search-selected" id="btn_list_parent" onclick="show();">A</div>
<div class="ui-search-selected-list" id="btn_List">
B
C
D
</div>
</div>
</body>
</html>