[Proposal] Extension of window.prompt to allow other types of common inputs (<select>, date, color, password, range, file, ...)

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"});
})();

image

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):

So it looks like there’s decent demand for this sort of thing.

Does anyone have any thoughts here?

Thanks!

2 Likes

My first question would be, do websites use the prompt() function? If yes, in what scenarios? If no, why not?

In its current form it’s obviously only useful for accepting text input, so I tend to see it used in cases where a small bit of text input is needed - i.e. not in cases where some other data is needed at the same time. You could do that with sequential prompts, but it would be terrible UX. As an example, glitch.com uses it when renaming files in the file tree.

If you look at those stack overflow links, you can see that many hundreds of thousands of developers are wanting to use confirm() but struggling with the lack of customizability. Many just want to have two buttons: “yes” and “no”.

I’ve had that problem before too, when all I wanted to do was ask the user whether they wanted a high-quality render, or a low-quality/fast render. I could have asked “Would you like a high-quality render? It will take a bit longer.”, but okay/cancel doesn’t work because “cancel” sounds like it means “cancel the whole task”. Later, I added a third render method, and in that case a simple drop-down menu with three items (or a prompt with 3 buttons) would have been perfect.

The main use case for me though would be in being able to quickly whip up demos and non-user-facing tools. It’s super handy when I’m coding up some sort of simulation thing, and I don’t want to have to worry about the DOM at all. I just want to ask the user (usually me, in this case) something like “How many iterations?” or “Resolution?” and prompt() works well for that in a lot of cases. Having a drop-down menu would help in cases where the question is “Which algorithm? (foo, bar, blah)” and I’ve got to type in the one I want.

Also, it’s interesting that Deno is adding (or has added?) support for confirm and prompt. So if other input methods were added, that might be something worth thinking about. For example, adding a range slider might be interesting to implement via a command line interface. Although now that I think about it, you’d just use the arrow keys to slide along.

I think your example is both great and kinda answers your own question… in the sense that you can create the prompt you want using the form primitives already provided.

You can also use the tag to solve your proposed use case: Allowing you to create more complex pop-up UIs with forms.