Javascript Popups and Promises

I was using confirm in Javascript to get a user to, well, confirm an action, but it looks very ugly, and after a few confirmation, the browser starts to get obnoxious: would you like to block further popups from this site? Also, it’s limited to a very simple text string question, and I had in mind to provide further options, or more text with better explanations. So I thought to add an HTML popup that would function modally, and because it all linked with various API calls, I wanted the html_confirm function to return a promise, that would resolve if the user accepted the dialog:

// old code
if (confirm(`Do you want to delete this?`)) {
	the_api.delete(postId);
}

should rather become:

html_confirm(`Do you want to delete this?`).then( ()=>the_api.delete(postId) );

The popup HTML is simple enough:

<div id="html_confirm">
	<div>
		<div class="titlebar">Confirm</div>
		<div class="content">
			Our message will go here.
		</div>
		<div class="buttons">
			<button class="button-cancel">No</button>
			<button class="button-continue">Yes</button>
		</div>
	</div>
</div>

We will use that id to find the form on the page, although in a final version of this, we would generate the HTML in the Javascript popup class. I would probably use a very template library (https://github.com/craigmj/dtemplate) to do that, but we’ll leave that aside for now.

The outer #html_confirm fills the whole screen and provides a semi-transparent background. The inner div is the actual popup prompt, containing a title bar, a message, and No and Yes buttons. Here’s the CSS, built with flexboxes in flexboxes.

#html_confirm {
	position: absolute;
	margin:  0; padding: 0;
	top: 0; left: 0; bottom: 0; right: 0;
	
	display: flex; 
	flex-direction: row; 
	justify-content: center; 
	align-items:  center;
	
	background-color:  rgba(0,0,0,0.7);
}
#html_confirm.hidden {
	display: none;
}

#html_confirm>div {
	background-color: white;
	padding:  1em;
	border:  1px solid #ccc;

	display: flex; 
	flex-direction: column; 
	justify-content:  flex-start; 
	align-items:  stretch;
	gap:  0.6em;
}

#html_confirm .titlebar {
	text-align: center;
	font-weight: bold;
}

#html_confirm .buttons {
	display: flex; 
	flex-direction: row; 
	justify-content: flex-end; 
	align-items: baseline;
	
	gap: 1em;
}

Let’s code the javascript for the popup. We’ll start with the code to display the popup. This is really easy:
1. Find the popup html on the page;
2. Set the message text;
3. Show the popup by removing the hidden class.

function html_confirm(message) {
	let el = document.getElementById(`html_confirm`);
	el.querySelector(`.content`).innerHTML = message;
	el.classList.remove(`hidden`);
}

document.addEventListener(`DOMContentLoaded`, function() {
	html_confirm(`Do you want to delete this?`);
});

Opening the web page at this point will show that it’s working as expected.

Let’s get the Promise working. We’re going to do this by going back to the basic constructor of a Promise:

new Promise(function(resolve, reject) {...});

A Promise is constructed with a single argument: a function that receives two parameters, a resolve function to be called if the Promise is to continue, and a reject function to be called if the Promise is rejected. We will call resolve() if the user clicks Yes, and reject() if the user clicks No, and we will return the Promise :

function html_confirm(message) {
	return new Promise( function(resolve, reject) {	
		// find our html	
		let el = document.getElementById(`html_confirm`);
		// set our content
		el.querySelector(`.content`).innerHTML = message;
		// wire the no and yes buttons
		let hide = function() { el.classList.add(`hidden`); }
		el.querySelector(`.button-cancel`).addEventListener(`click`, function() {
			hide();
			reject();
		});
		el.querySelector(`.button-continue`).addEventListener(`click`, function() {
			hide();
			resolve();
		});
		// show the popup
		el.classList.remove(`hidden`);
	});
};

We use it like this:

document.addEventListener(`DOMContentLoaded`, function() {
	html_confirm(`Do you want to delete this?`)
	.then( 
		()=>alert(`you chose to continue`),
		()=>console.log(`not continuing`)
	)
	.catch( ()=>console.error(`caught an error`) );
});

Note that in our .then() to html_confirm, we provide both a resolve and a reject callback. We don’t want a cancel option to fall through as an error, which would happen if we don’t handle the reject in that then() call.

That’s modal popups in 16 lines of Javascript code, and 34 lines of CSS. This used to require libraries in the past. Of course we could make it more complicated, could add a title bar, and a z-index, but we’ve got an incredibly simple, very elegant popup. We could add a close button, which would have the same effect as clicking No, or handle pressing escape as a keyboard shortcut. And we can style this exactly as we want.

We’ve also got a very basic confirmation popup that is incredibly easy to maintain because there’s almost nothing to it.

Eventually, we will have dialog built into the browser (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) and this will make popups more of a first-class browser function, but it’s not difficult at the moment. Everytime I see a website using React to get a basic popup, I think of how resources are wasted in the modern world…

the lateral alternative

32 years exquisite software