I often find myself wanting a quick modal to accept some input, but I don’t want to include a big dependency, or manually code up something. And I’m obviously not alone here: I see window.prompt
and window.confirm
used quite often on the web - even in large applications.
I’m a huge fan of having “simple things be simple, and complex things be possible”, as the saying goes, and in the case where I just want to quickly grab some user input, I think it should be as simple as prompt(...)
.
An async version of this is “polyfill-able”. Here’s a very rough sketch:
async function prompt2(message, opts) {
let ctn = document.createElement("div");
let type = opts.type;
let input;
if(type == "select") {
input = `<select style="width:100%;height:100%;background:white;color:inherit;font:inherit;box-sizing:border-box;">${opts.options.map(o => `<option value="${o.value}">${o.content}</option>`).join("")}</select>`;
} else if(type == "buttons") {
input = opts.buttons.map(o => `<button style="height:100%;margin-right:0.5rem;font:inherit;box-sizing:border-box;" data-value="${o.value}">${o.content}</button>`).join("");
} else {
input = `<input style="width:100%;height:100%;background:white;color:inherit;font:inherit;box-sizing:border-box;" type="${opts.type}">`;
}
ctn.innerHTML = `
<div style="background:rgba(0,0,0,0.2); position:fixed; top:0; left:0; right:0; bottom:0; z-index:9999999; display:flex; justify-content:center; color:black; font-family: sans-serif;">
<div style="width:400px; background:white; height: min-content; padding:1rem; border:1px solid #eaeaea; border-radius:3px; box-shadow: 0px 1px 10px 3px rgba(0,0,0,0.24); margin-top:0.5rem;">
<div style="opacity:0.6;">${window.location.hostname} says:</div>
<div style="margin:1rem 0;">${message}</div>
<div style="display:flex; height:2.3rem;">
<div style="flex-grow:1; padding-right:0.5rem;">${input}</div>
${type !== "buttons" ? `<button style="font:inherit;box-sizing:border-box;">Submit</button>` : ""}
</div>
</div>
</div>
`;
document.body.appendChild(ctn);
let value = await new Promise((resolve) => {
if(type !== "buttons") {
ctn.querySelector("button").onclick = () => {
if(type == "file") {
resolve(ctn.querySelector("input").files);
} else {
resolve(ctn.querySelector("input,select").value);
}
}
} else {
ctn.querySelectorAll("button").forEach(b => {
b.onclick = () => resolve(b.dataset.value);
});
}
});
ctn.remove();
return value;
}
Usage:
let name = await prompt2("Please type your name:", {type:"text"});
let pw = await prompt2("Please type your password:", {type:"password"});
let date = await prompt2("Please choose a date:", {type:"date"});
let files = await prompt2("Please choose a file:", {type:"file"});
let choice = await prompt2("Please choose an option:", {type:"select", options:[{content:"Thing 1", value:"1"}, {content:"Thing 2", value:"2"}]});
let choice = await prompt2("Please choose an option:", {type:"buttons", buttons:[{content:"Thing 1", value:"1"}, {content:"Thing 2", value:"2"}]});
// Demo:
(async function() {
window.prompt2 = await import("https://deno.land/x/gh:josephrocca:prompt2@v0.0.2/mod.js").then(m => m.default);
let date = await prompt2("Please choose a date:", {type:"date"});
})();
Obviously window.prompt
can’t be made async, and the second param of window.prompt
is already used for the default value, so perhaps it would be better to start over with a new function. Although it would be great to have a sync version of this API too - so that we don’t have to sprinkle async
throughout a codebase that was completely sync up until that point.
I wonder if it would be safe enough to use the second param of window.prompt
? I doubt anyone is actually passing an object to the second parameter on purpose, so that they can have [object Object]
as the default value. And then a window.promptAsync
could be added which has the same UI, but returns a promise. But I’m really just thinking out loud here.
showModal is not what I’m looking for here, because it requires much more than a simple one-liner like prompt(...)
.
I noticed that there are over a million views on these questions simply asking how to customise the confirm
prompt (either by adding an extra button or changing okay/cancel to yes/no):
- https://stackoverflow.com/questions/9091001/how-to-show-confirmation-alert-with-three-buttons-yes-no-and-cancel-as-it
- https://stackoverflow.com/questions/823790/javascript-confirm-popup-yes-no-button-instead-of-ok-and-cancel
- https://stackoverflow.com/questions/11046572/make-javascript-alert-yes-no-instead-of-ok-cancel
- https://stackoverflow.com/questions/9334636/how-to-create-a-dialog-with-yes-and-no-options
So it looks like there’s decent demand for this sort of thing.
Does anyone have any thoughts here?
Thanks!