Modal

Modal opens an existing app page in a fullscreen native <dialog> and returns one value to a callback. The page inside is loaded by URL in an <iframe>, so it is an ordinary page, not a rebuilt view.

Source: Modal.js, Modal.html

<button onclick="Modal.Open('/items', (item) =>
    item && (this.textContent = item.name)
)">Choose</button>

Clicking opens /items in the modal. The callback runs on close. It gets the chosen item, or null when the modal is cancelled. Here a cancel does nothing, but the parent can act on null if it needs to, such as clearing a state.

The page loaded in the iframe returns a value with Result, or closes with Cancel. Esc on the body routes Escape to Cancel. It looks like this:

<body onkeydown="Modal.Esc(event)">
  <ul>
    <li><button onclick="Modal.Result({ id: 1, name: 'Item 1' })">Item 1</button></li>
    <li><button onclick="Modal.Result({ id: 2, name: 'Item 2' })">Item 2</button></li>
  </ul>
  <button onclick="Modal.Cancel()">Cancel</button>
</body>

Functions

Modal.Open(url, callback) opens the dialog. It loads url in the iframe. callback runs when the modal closes, with the returned value or null on cancel.

Modal.Result(data) is called from the page inside the iframe. It closes the modal and runs the callback with data.

Modal.Cancel() is called from the page inside the iframe. It closes the modal and runs the callback with null.

Modal.Esc(event) is called from the page inside the iframe, on onkeydown. Escape closes the modal the same as Cancel.

Any page as a modal

This is the point of the pattern, and it is optional. Add _modal=1 to any URL. The page reads the flag and renders itself as a modal: no site chrome, transparent body, content in the center. The same URL without the flag is a normal full page. With one flag every view the app already has can open as a modal. No modal version of a screen is built.

Reusing a list

An existing list opens as a picker, and it keeps working inside the modal. Its filter, search, and paging reload the iframe the same as on a full page. To pick a row, the row posts its own id back to the same list. The controller takes the id, loads that one row's detail or renders an HTML fragment, and returns it with Modal.Result. The list carries ids, not the full detail of every row, and that detail is built only for the row chosen.

Loading

The dialog opens once the page is ready. A fast load shows the modal already full. A slow load shows a loading state with a Cancel button first, then flips to the page. An iframe reports no failed load, so a page that never answers stays on loading, and Cancel is the way out.

Support

Modal uses the native <dialog> element and showModal. It works in Chrome 37+, Firefox 98+, and Safari 15.4+. It does not use CSS anchor positioning, so it has no Chromium-only limit.

Next: Notify →