I would like to simply delete an HTML element via JavaScript when it is clicked and after the user has provided confirmation.
After the click but before the confirmation, I want the element to be highlighted (have a different border color). However, in the code below, the confirmation is happening before the border color changes.
css:
.test-box {
border: 2px solid black;
height: 200px;
width: 200px;
cursor: pointer;
}
js:
document.addEventListener('DOMContentLoaded', () => {
const testBox = document.createElement('div')
testBox.className = "test-box"
document.body.append(testBox)
// Remove testBox when clicked
testBox.addEventListener('click', () => {
// Highlight testBox to be deleted
testBox.style.border = "2px solid yellow"
// Confirm deletion
const confirmation = confirm("Delete testBox?")
if (confirmation) {
testBox.remove()
} else {
// Remove highlight
testBox.style.border = "2px solid black"
}
})
})
The only way I can get it to work as desired is by adding a 1ms timeout to the confirmation, like this:
document.addEventListener('DOMContentLoaded', () => {
const testBox = document.createElement('div')
testBox.className = "test-box"
document.body.append(testBox)
// Remove testBox when clicked
testBox.addEventListener('click', () => {
// Highlight testBox to be deleted
testBox.style.border = "2px solid yellow"
// Delay confirmation so that highlight will happen before confirmation
setTimeout(() => {
// Confirm deletion
const confirmation = confirm("Delete testBox?")
if (confirmation) {
testBox.remove()
} else {
// Remove highlight
testBox.style.border = "2px solid black"
}
}, 1)
})
})
Is this the best way around this? I want to understand what is happening.
CodePudding user response:
confirm
, alert
and input
prevent the JavaScript engine from processing the browser's event queue which would include a paint job.
One way to allow a paint cycle to be executed is to use requestAnimationFrame
: that will execute the given callback right before the paint job, and if you there call requestAnimationFrame
again, that one will call its callback after that paint job, just before the next one. You can use this behaviour to resolve a promise after the next paint cycle. Define the event handler as async
and await that promise:
const paintCycle = () => new Promise(resolve =>
requestAnimationFrame(() => requestAnimationFrame(resolve))
);
const testBox = document.createElement('div')
testBox.className = "test-box"
document.body.append(testBox)
// Remove testBox when clicked
testBox.addEventListener('click', async () => {
// Highlight testBox to be deleted
testBox.style.border = "2px solid yellow"
await paintCycle();
// Confirm deletion
const confirmation = confirm("Delete testBox?")
if (confirmation) {
testBox.remove()
} else {
// Remove highlight
testBox.style.border = "2px solid black"
}
})
.test-box:after {
content: "click me"
}
CodePudding user response:
Updates to the DOM are not immediate and do take some time to complete. In this case, your update is happening within the 1ms your setTimeout()
function waits before firing off the call to confirm()
. That's why delaying that function call is working. Without the setTimeout
the Javascript thread calls confirm
before your DOM finishes its update. This article does a good job explaining what is happening and what you can do to avoid it.
CodePudding user response:
Because:
- JavaScript is single-threaded.
- Updates to the UI won't occur until the blocking operations are complete.
confirm()
is a blocking operation.
If you want to use confirm()
then the approach you've come up with is a reasonable workaround for the UX you're trying to achieve. A more modern and clean approach would be to not use confirm()
(or alert()
or prompt()
) at all and build a modal confirmation dialog into the page. (Or use a 3rd party one, of which there are many.)