How to disable click events when execution is going on in React? - javascript

I have a screen
where I want to disable all the events when execution is going on.
When I click on the Execute button, an API is called which probably takes 4-5 minutes to respond. During that time, I don't want the user to click on the calendar cells or Month navigation arrows.
In short, I want to disable all the events on the center screen but not the left menu.
Is it possible to do that?

Yes sure, you can add a class with css pointer-events rule. Set it on the whole table and it will disable all events. Just add it when request starts and remove it when it ends. You can achieve that, by having a boolean variable isLoading in your state, or redux store and based on that add or remove the no-click class.
.no-click {
pointer-events: none;
}

You can use classic loading overlay box over your content when some flag (i.e. loading) is true.
Other way to do it is to use pointer-event: none in CSS. Use same flag to set class to your content block.
Here is a working example in codesanbox:
https://codesandbox.io/s/determined-dirac-fj0lv?file=/src/App.js
Here is code:
export default function App() {
const [loadingState, setLoadingState] = useState(false);
const [pointerEvent, setPointerEvent] = useState(false);
return (
<div className="App">
<div className="sidebar">Sidebar</div>
<div
className={classnames("content", {
"content-pointer-event-none": pointerEvent
})}
>
<button onClick={() => setLoadingState(true)}>
Show loading overlay
</button>
<button onClick={() => setPointerEvent(true)}>
Set pointer event in css
</button>
{loadingState && <div className="loading-overlay"></div>}
</div>
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
display: flex;
position: absolute;
width: 100%;
height: 100%;
}
.content {
flex: 1;
position: relative;
}
.loading-overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 1;
}
.content-pointer-event-none {
pointer-events: none;
}

Related

Trying to animate a snackbar in Svelte, using different animations for when it appears and disappears

My current goal is to build a Material-esque snackbar component that will appear from the bottom of the screen, and then blur once the action to dismiss the snackbar is taken. My current code looks like this
<script lang="ts">
import '../css/snackbar.css';
import { linear } from 'svelte/easing';
import { blur, fly } from 'svelte/transition';
let show = true;
export let message: string;
export let actionText: string = 'Dismiss';
let options = { duration: 350, easing: linear };
</script>
{#if show}
<div
class="snackbar"
class:snackbar-active={Boolean(message)}
transition:blur={options}
>
<div class="snackbar-text">
{message}
</div>
<button class="link" on:click={() => (show = !show)}>{actionText}</button>
</div>
{:else}
<div
class="snackbar"
class:snackbar-active={Boolean(message)}
transition:fly={{ ...options, opacity: 1, y: 600 }}
>
<div class="snackbar-text">
{message}
</div>
<button class="link" on:click={() => (show = !show)}>{actionText}</button>
</div>
{/if}
And the CSS:
.snackbar {
align-items: flex-end center;
background: var(--project-color-surface-variant);
border-radius: 0.5rem;
color: #fff;
display: inline-flex;
justify-content: flex-end center;
padding: 0.625rem 1.25rem 0.625rem 1.25rem;
position: fixed;
width: fit-content;
z-index: 9;
}
.snackbar .link {
background: none;
border: none;
color: var(--project-color-secondary);
display: inline-flex;
padding-left: 1.25rem;
}
In it's current state, the snackbar will initially appear from the bottom of the screen, and then blur away on clicking the actionText. But then it will immediately fly back in from the top, then blur on click, and start the cycle over by appearing from the bottom.
What I want is for it to appear whenever it is triggered on a page, and then when it is clicked, it will disappear without coming back.
I've tried CSS keyframes for other transitions but I couldn't quite get it the way I wanted. When I discovered the transitions that Svelte had built-in, I wanted to try those. They're nice, I just want this instance to work the way I want it to.
The problem is your if/else, there should only be one the if branch and you can switch out the transitions using in: and out:.
{#if show}
<div
class="snackbar"
class:snackbar-active={Boolean(message)}
in:fly={{ ...options, opacity: 1, y: 600 }}
out:blur={options}>
<div class="snackbar-text">
{message}
</div>
<button class="link" on:click={() => (show = !show)}>{actionText}</button>
</div>
{/if}
REPL

closing modal on window vanilla js

I've been trying to figure out how to close the modal I made by a window click but have no success. My modal is activated by adding a class and removing the 'show-modal' class. So I created the window event listener and seems like whenever I press the "a" tag in my HTML, it also considers that click as part of the window so the modal won't even open.
However, once I remove the window event listener, the modal is working fine; but the modal does not close unless I hit the "closeModal" button.
Is there something I am doing wrong or improve on? I tried googling around but their idea is different than mine.
I know I can do this using React & Boostrap but I am also trying to learn the vanilla JS way so I want to do this right!
const contact = document.querySelector('.contact');
const modal = document.querySelector('.modal');
const closeModal = document.querySelector('.closeBtn');
contact.addEventListener('click', showModal);
closeModal.addEventListener('click', modalClose);
window.addEventListener('click', modalClose);
function showModal(){
modal.classList.add('show-modal');
console.log('clicked')
}
function modalClose(){
modal.classList.remove('show-modal');
console.log('closed')
}
One way to get around this would be to add an "underlay" to the modal and listen for clicks on that instead of the window element.
Also I like to add the modal open class to the body so then it's easy to adjust styling on both the modal and modal underlay.
document.querySelector('button#open').addEventListener('click', e => {
document.body.classList.add('show-modal')
})
document.querySelector('button#close').addEventListener('click', e => {
document.body.classList.remove('show-modal')
})
document.querySelector('.underlay').addEventListener('click', e => {
document.body.classList.remove('show-modal')
})
.modal {
height: 100px;
width: 100px;
background-color: white;
display: none;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.underlay {
display: none;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.show-modal .modal {
display: block;
}
.show-modal .underlay {
display: block;
}
<button id="open">Open Modal</button>
<div class="underlay"></div>
<div class="modal">
<button id="close">Close Modal</button>
</div>
</div>
You can use event.stopPropagation() to avoid event propagation, try this:
function showModal(){
event.stopPropagation();
modal.classList.add('show-modal');
console.log('clicked')
}

CSS Transition Not Working with Javascript [duplicate]

This question already has answers here:
Transitions on the CSS display property
(37 answers)
Closed 3 years ago.
I'm sure that I'm making a rookie mistake here, but I've researched this solution all day, and I can't seem to understand why my code below isn't working.
The use case is a button that opens up a modal box inside a semi-transparent overlay, with the overlay covering everything else on the screen, including the button that opened it. The button is currently opening the modal and overlay just fine, and clicking anywhere outside of the modal box does indeed close it. But I don't understand why my set CSS transition isn't working.
I'm at a loss on this one, so I'd very much appreciate any advice that more seasoned developers can offer. Thank you so much in advance!
Best,
Josh
var modalOverlay = document.getElementById('modalOverlay');
var modalButton = document.getElementById('modalButton');
modalButton.addEventListener('click', openModal);
window.addEventListener('click', closeModal);
function openModal() {
modalOverlay.style.display = "flex";
modalOverlay.style.opacity = "1";
}
function closeModal(event) {
if (event.target == modalOverlay) {
modalOverlay.style.opacity = "0";
modalOverlay.style.display = "none";
}
}
.modal-overlay {
display: none;
opacity: 0;
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
background-color: rgba(0,0,0,0.5);
transition: all 1s ease-in-out;
}
.modal-box {
width: 200px;
height: 200px;
background-color: blue;
}
<button id="modalButton" class="modal-button">Open Modal</button>
<div id="modalOverlay" class="modal-overlay">
<div id="modalBox" class="modal-box"></div>
</div>
display is not a property that can be transitioned. You need to make your animation take multiple steps. When you click the button, the modal should be made flex, but it should still be transparent. Then you need to transition the opacity up to 1 which is what CSS transitions can do.
You need to do the inverse whenever you close the modal. Transition back to opacity 0 and after the transition is done, mark it display: none.
var modalOverlay = document.getElementById('modalOverlay');
var modalButton = document.getElementById('modalButton');
modalButton.addEventListener('click', openModal);
window.addEventListener('click', closeModal);
function openModal() {
// This will cause the browser to know
// that the element is display flex for a frame
requestAnimationFrame(() => {
modalOverlay.classList.add("modal-overlay--open");
// Then when we wait for the next frame
// the browser will now know that it needs
// to do the transition. If we don't make
// them separate actions, the browser
// will try to optimize the layout and skip
// the transition
requestAnimationFrame(() => {
modalOverlay.classList.add("modal-overlay--open-active");
});
});
}
function closeModal(event) {
if (event.target == modalOverlay) {
modalOverlay.classList.remove("modal-overlay--open-active");
setTimeout(() => {
modalOverlay.classList.remove("modal-overlay--open");
}, 1100);
}
}
.modal-overlay {
display: none;
opacity: 0;
position: fixed;
top: 0;
left: 0;
z-index: 10;
width: 100vw;
height: 100vh;
align-items: center;
justify-content: center;
background-color: rgba(0,0,0,0.5);
transition: all 1s ease-in-out;
}
.modal-overlay.modal-overlay--open {
display: flex;
}
.modal-overlay.modal-overlay--open-active {
opacity: 1;
}
.modal-box {
width: 200px;
height: 200px;
background-color: blue;
}
<button id="modalButton" class="modal-button">Open Modal</button>
<div id="modalOverlay" class="modal-overlay">
<div id="modalBox" class="modal-box"></div>
</div>
Let's draw a little insight from how some other frameworks deal with these kinds of transitions. For example, Vue.js separates its enter/leave transitions into 6 sorts of phases:
Enter: Starting state, added before entry (for you, display: flex and fully transparent)
Enter Active: The transitioning state that sets the transition "target" (opacity towards 1 in your case)
Enter To: What it should be after the transition is complete (we aren't going to bother with this)
Leave: About to start leaving (nothing really needs to change here for us)
Leave Active: Set the target state for your element so that it knows what to transition to (for us, we just remove the class that says opacity: 1)
Leave To: We don't need this either
The main thing that we need to consider is that we need the browser to have the element in the page and "rendered" so that it will consider it for transitioning. That's why we add, in our example, the modal-overlay--open class which makes it flex. We then wait just a second and add the transition target class modal-overlay--open-active which causes the element to actually transition.
Then we do the same thing in reverse: remove modal-overlay--open-active so the browser knows to transition the element back to the "normal" style. We set a timeout to remove the display: flex class after the transition is done. You could use event listeners for this, but it's overkill for such an example.
The display property doesn't play well with transition. Instead, just toggle between opacity 1 and 0.
const modalOverlay = document.getElementById('modalOverlay');
const modalButton = document.getElementById('modalButton');
modalButton.addEventListener('click', openModal);
window.addEventListener('click', closeModal);
function openModal() {
modalOverlay.style.opacity = "1";
modalButton.style.opacity = "0";
}
function closeModal(event) {
if (event.target == modalOverlay) {
modalOverlay.style.opacity = "0";
modalButton.style.opacity = "1";
}
}
.modal-overlay {
opacity: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-overlay,
.modal-button {
transition: opacity 1s ease-in-out;
}
.modal-box {
width: 200px;
height: 200px;
background-color: blue;
}
.modal-button {
position: absolute;
z-index: 2;
}
<button id="modalButton" class="modal-button">Button</button>
<div id="modalOverlay" class="modal-overlay">
<div id="modalBox" class="modal-box"></div>
</div>

Prevent modal from closing when click didn't initiate outside of content?

I've created a simple modal that is allowed to be closed when you click outside of the content area. This is by design but it has an unintended side-effect. If I click anywhere in the content area (for example in a text field) and drag the mouse to beyond the content area and then release the click it will close the modal. I often have a habit of doing this and I can see how average users will perceive this as a bug so I'm trying to nip it prior to release.
var modal = document.getElementById("modal-container");
function openModal() { modal.classList.add("active"); }
function closeModal() { modal.classList.remove("active"); }
window.onclick = function (event) {
if (event.target == modal)
closeModal();
}
html, body {
margin: 0;
height: 100%;
}
.modal-container.active { top: 0; }
.modal-container {
position: absolute;
top: -500vh;
left: 0;
width: 100%;
height: 100%;
display: grid;
background-color: rgba(0, 0, 0, 0.75);
}
.modal-content {
height: 50%;
width: 50%;
margin: auto;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
<button onclick="openModal();">Open the Modal</button>
<div id="modal-container" class="modal-container">
<div class="modal-content">
<input type="text" />
</div>
</div>
To test it properly:
Click the 'Open the Modal' button.
Click in the text box at the center of the white panel.
Enter some text.
Press the left mouse button down in the text box.
Drag the mouse beyond the bounds of the white panel.
Release the mouse button.
The modal should now be closed.
Is there a way to prevent this without tracking the coordinates of the mouse?
Perhaps onmousedown instead of click?
That worked! Just need more coffee this morning I suppose. Going to write up a thorough answer later today for future readers.
Before you answer yourself with a valid cause (as noted in your Question Edit) -
take in consideration:
onmousedown might not always be the desired UX. (Sometimes experienced users to undo a mousedown not being registered as a click they on purpose move the mouse over another element for the mouseup event just to retain the current state.)
Remove inline JavaScript
Assign listeners using Element.addEventListener() to any button having the data-modal attribute
Use data-modal="#some_modal_id" even no the container element
Finally: use if (evt.target !== this) return;
const el_dataModal = document.querySelectorAll('[data-modal]');
function toggleModal(evt) {
if (evt.target !== this) return; // Do nothing if the element that propagated the event is not the `this` button which has the event attached.
const id = evt.currentTarget.getAttribute('data-modal');
document.querySelector(id).classList.toggle('active');
}
el_dataModal.forEach(el => el.addEventListener('click', toggleModal));
html, body {
margin: 0;
height: 100%;
}
.modal-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
background-color: rgba(0, 0, 0, 0.75);
opacity: 0; /* ADDED */
transition: 0.26s; /* ADDED */
visibility: hidden; /* ADDED */
}
.modal-container.active {
opacity: 1; /* ADDED */
visibility: visible; /* ADDED */
}
.modal-content {
height: 50%;
width: 50%;
margin: auto;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
<button data-modal="#modal-container">Open the Modal</button>
<div id="modal-container" class="modal-container" data-modal="#modal-container">
<div class="modal-content">
<input type="text">
<br><br>
<button data-modal="#modal-container">CLOSE MODAL TEST</button>
</div>
</div>
This is working example. Think, it matches that one you need))
var clickTarget = null;
var modal = document.getElementById("modal-container");
function openModal() {
modal.classList.add("active");
document.body.addEventListener('mousedown', onModalMouseDown, false);
document.body.addEventListener('mouseup', onModalMouseUp, false);
}
function closeModal() {
modal.classList.remove("active");
document.body.removeEventListener('mousedown', onModalMouseDown);
document.body.removeEventListener('mouseup', onModalMouseUp);
}
function onModalMouseDown(event) {
clickTarget = event.target;
}
function onModalMouseUp() {
if (clickTarget === modal) {
closeModal();
}
}
html, body {
margin: 0;
height: 100%;
}
.modal-container.active { top: 0; }
.modal-container {
position: absolute;
top: -500vh;
left: 0;
width: 100%;
height: 100%;
display: grid;
background-color: rgba(0, 0, 0, 0.75);
}
.modal-content {
height: 50%;
width: 50%;
margin: auto;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.modal-trigger-btn {
margin: 20px;
font-size: 16px;
}
<button onmousedown="openModal();" class="modal-trigger-btn">Open the Modal</button>
<div id="modal-container" class="modal-container">
<div class="modal-content">
<input type="text" placeholder="Start to drag outside..."/>
</div>
</div>
To answer this question myself, I thought about how the onclick event was working. A click is defined as the mouse button being pressed down, and then released. Both of those points have to occur to cause the onclick event to be raised (though you can't really have one without the other happening at some point before or after).
I haven't found any real documentation on the execution path below so it based on logical deduction. If you have any documentation on this please link it in a comment so that I can review it and adjust my answer for future readers.
User presses down the mouse button.
The onmousedown event is raised.
User releases the mouse button.
The onmouseup event is raised.
The onmouseclick event is raised.
I did write a test up to verify these results:
var ePath = document.getElementById("executionPath");
document.body.onmousedown = function (event) { ePath.innerHTML += "On Mouse Down<br>"; }
document.body.onmouseup = function (event) { ePath.innerHTML += "On Mouse Up<br>"; }
document.body.onclick = function (event) { ePath.innerHTML += "On Click<br>"; }
html, body { height: 100%; }
<p id="executionPath">Click the Window<br></p>
I believe the unintended behavior is caused by when the target is set for the onclick event. I think there are three possibilities (below from most to least likely) for when this is set, none of which I can confirm or deny:
The target is set when the mouse button is released.
The target is set when the mouse button is pressed down, then again when the mouse button is released.
The target is set continuously.
After analyzing my thoughts I determined that for my scenario onmousedown is likely to be the best solution. This will ensure that the modal closes only if the user initiates the click outside of the content area. A good way to couple this with onmouseup to ensure a full click is still achieved is demonstrated below. Though in my case I am okay with simply using onmousedown:
var initialTarget = null;
var modal = document.getElementById("modal-container");
function openModal() { modal.classList.add("active"); }
function closeModal() { modal.classList.remove("active"); }
window.onmousedown = function (event) { initialTarget = event.target; }
window.onmouseup = function (event) {
if (event.target == initialTarget)
closeModal();
}
html, body { height: 100%; }
.modal-container.active { top: 0; }
.modal-container {
position: absolute;
top: -500vh;
left: 0;
width: 100%;
height: 100%;
display: grid;
background-color: rgba(0, 0, 0, 0.75);
}
.modal-content {
height: 50%;
width: 50%;
margin: auto;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
<button onclick="openModal();">Open the Modal</button>
<div id="modal-container" class="modal-container">
<div class="modal-content">
<input type="text" />
</div>
</div>
The snippet above ensures that the click starts and ends on the modal container prior to closing the modal. This prevents the modal from closing if the user accidentally initiates a click outside of the content area and drags their mouse into the content area to complete the click. The same is true in the reverse order, and the modal will only close if the click is initiated and completed on the modal container.
The only thing I can't figure out is when the target for onclick is set which is probably more important in a proper explanation on the root cause of this issue so feel free to let me know!

Changing elements (like fixed logo) color, depending on the section in React

I want to achieve an effect like this one
in a React webpage but without using jQuery. I've looked for alternatives to that library, but without results. I've seen a lot of similar questions, but each of them are answered using jQuery.
The effect is basically changing the color of the logo (and other elements in the page) as I scroll down through different sections.
Does anyone know a way to achieve this?
A way this could be done is by centering the logo's to their own containers dynamically, kinda like simulating position fixed, but using position absolute, so each logo is contained in their own section, and not globally like position fixed would do.
This way when you scroll to the next section, the second section covers the first section making it look like its transitioning.
I created a proof of concept here:
https://codesandbox.io/s/9k4o3zoo
NOTE: this demo is a proof of concept, it could be improved in performance by using something like request animation frame, and throttling.
Code:
class App extends React.Component {
state = {};
handleScroll = e => {
if (!this.logo1) return;
const pageY = e.pageY;
// 600 is the height of each section
this.setState(prevState => ({
y: Math.abs(pageY),
y2: Math.abs(pageY) - 600
}));
};
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
render() {
const { y, y2 } = this.state;
return (
<div>
<section className="first">
<h1
className="logo"
style={{ transform: `translateY(${y}px)` }}
ref={logo => {
this.logo1 = logo;
}}
>
YOUR LOGO
</h1>
</section>
<section className="second">
<h1
className="logo"
style={{ transform: `translateY(${y2}px)` }}
ref={logo => {
this.logo2 = logo;
}}
>
YOUR LOGO
</h1>
</section>
</div>
);
}
}
CSS would be:
section {
height: 600px;
width: 100%;
position: relative;
font-family: helvetica, arial;
font-size: 25px;
overflow: hidden;
}
.first {
background: salmon;
z-index: 1;
}
.first .logo {
color: black;
}
.second {
background: royalBlue;
z-index: 2;
}
.second .logo {
color: red;
}
.logo {
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 230px;
height: 30px;
}

Categories