Edit this page

API Closing overlays

This page explains the many different ways to close an overlay in Unpoly.

In addition to making the overlay disappear, you may also communicate a result value back to the parent layer. This is useful to branch off a complex user interaction into an overlay, then resume the original scenario when you're done.

Distinguishing close intents

When closing an overlay, Unpoly distinguishes between two kinds close intents:

  1. Accepting an overlay (user picks a value, confirms with "OK", etc.), optionally with a value
  2. Dismissing an overlay (user clicks "Cancel", "X", presses ESC, clicks on the background)

Different ways to close a modal overlay

Accepting an overlay will usually continue a larger interaction in the parent layer. Dismissal usually means cancelation. When you're waiting for a subinteraction to finish successfully you're interested layer acceptance, but not dismissal.

Running code when an overlay closes

When opening a layer you may pass separate { onAccepted } and { onDismissed } callbacks to handle both close intents:

up.layer.open({
  url: '/users/new',
  onAccepted: (event) => console.log('User was created'),
  onDismissed: (event) => console.log('User creation was canceled')
})

Both callbacks are optional.

In HTML you may use [up-on-accepted] and [up-on-dismissed] attributes for the same purpose:

<a href="/select-user"
  up-layer="new"
  up-on-accepted="console.log('User was created')"
  up-on-dismissed="console.log('User creation was canceled')">>
  ...
</a>

Overlay result values

Overlays in Unpoly may be closed with an optional result value. The result value is communicated back to the layer that opened the overlay. Result values can be used to augment a select or navigate to another screen.

Acceptance values

E.g. if the user selects a value, creates a record or confirms an action, we consider the overlay to be accepted with that value.

You can access the acceptance value from an { onAccepted } callback:

up.layer.open({
  url: '/select-user',
  onAccepted: (event) => console.log('Got user', event.value)
})

The acceptance value may also be accessed when you're opening layers from HTML:

<a href="/select-user"
  up-layer="new"
  up-on-accepted="console.log('Got user', value)">
  ...
</a>

Dismissal reasons

When an overlay is dimissed, the result value can indicate the reason for dismissal. For instance, closing an overlay by clicking on the × symbol will dismiss with the value ":button".

The reason can be accessed from an { onDismissed } callback or [up-on-dismissed] attribute:

<a href="/select-user"
  up-layer="new"
  up-on-dismissed="console.log('Dismissal reason is', value)">
  ...
</a>

You can provide a dismissal reason when closing an overlay using up.layer.dismiss() or [up-dismiss].

If the overlay is closed using a standard dismiss control, the following default reasons are set:

User interaction Dismissal reason
User presses the Escape key ":key"
User clicks on the background ("light dismiss") ":outside"
User clicks on the × button in the overlay corner ":button"

Close conditions

When opening an overlay, you may define a condition when the overlay interaction ends. When the condition occurs, the overlay is automatically closed and a callback is run.

It is recommend to use close conditions instead of closing with explicit commands like up.layer.accept() or X-Up-Accept-Layer. By defining a close condition, the overlay content does not need to be aware that it's running in an overlay. The overlay interaction is decoupled from the interaction in the parent layer.

Closing when a location is reached

The following will open an overlay that closes once a URL like /companies/123 is reached:

<a href="/companies/new"
  up-layer="new"
  up-accept-location="/companies/$id"
  up-on-accepted="alert('New company with ID ' + value.id)">
  New company
</a>

Named segments captured by the URL pattern ($id) will become the overlay's acceptance value.

To dismiss an overlay once a given location is reached, use [up-dismiss-location] and [up-on-dismissed] in the same fashion.

Closing when an event is emitted

Instead of waiting for a location to be reached, you may accept an overlay
once a given event is observed on the overlay:

<a href="/users/new"
  up-layer="new"
  up-accept-event="user:created"
  up-on-accepted="alert('Hello user #' + value.id)">
  Add a user
</a>

When the user:created event is observed within the new overlay, the event's default action is prevented and the overlay is closed. The event object becomes the overlay's acceptance value.

To dismiss an overlay once a given event is observed, use the [up-dismiss-event] and [up-on-dismissed] attributes in the same fashion.

To emit an event, use one of the following methods:

Method Description
up.emit() JavaScript function to emit an event on any element
up.layer.emit() JavaScript function to emit an event on the current layer
[up-emit] HTML attribute to emit an event on click
X-Up-Events HTTP header sent from the server
Element#dispatchEvent() Standard DOM API to emit an event on an element

When an event causes an overlay to close, its default is prevented. You can use [up-emit] with a fallback URL to make a link that emits a closing event in an overlay, but navigates to a different page on the root layer.

Rendering discarded notification flashes

When an overlay closes in reaction to a server response, the content from that response is discarded. Any confirmation flashes would be lost.

The [up-flashes] element addresses this by picking up flashes from a closing overlay and rendering them into the parent layer.

Using the discarded response

When a server response causes an overlay to closes, no content from that response is rendered. Sometimes you need to access the discarded response, e.g. to render its content in another layer.

One way to achieve this is to use an [up-hungry] element with an [up-if-layer=subtree] attribute on a parent layer. A matching element would be updated with the discarded response of a closing overlay.

If you need more control, you may also access response via the { response } property of the up:layer:accepted and up:layer:dismissed events.

For example, the link link opens an overlay with a form to create a new company (/companies/new). After successful creation the form redirects to the list of companies (/companies). In that case we can use the HTML from the response and render it into the parent layer:

<a href="/companies/new"
   up-layer="new"
   up-accept-location="/companies"
   up-on-accepted="up.render('.companies', { response: event.response }"> <!-- mark-phrase "event.response" -->
  New company
</a>

The { response } property is available whenever a server response causes an overlay to close:

Closing from the server explicitly

If you don't want to use close conditions, the server may explicitly close an overlay by sending an X-Up-Accept-Layer or X-Up-Dismiss-Layer header. Optionally the header may transport a value.

When you're using the unpoly-rails gem, you may produce these headers with up.layer.accept(value) or up.layer.dismiss(value).

The server may also test if the fragment change is targeting an overlay by looking at the X-Up-Mode header.

When an overlay closes in reaction to a server response, you can access the discarded response.

Closing from JavaScript

If for some reason you cannot use a close condition, you may call up.layer.accept() to explicitly accept a layer from JavaScript:

up.layer.accept()

To accept with a value, pass it as an argument:

up.layer.accept({ name: 'Anna', email: 'anna@domain.tld' })

To dismiss an overlay from JavaScript, use the up.layer.dismiss() function in the same fashion.

Closing when an element is clicked

Use an [up-accept] or [up-dismiss] attribute to close the current layer when the link is clicked:

<a href="/fallback" up-accept>...</a>

If an overlay was closed, the click event's default action is prevented and the link will not be followed. Only when this link is clicked in the root layer, there is no overlay to close and the link to /fallback will be followed.

To dismiss an overlay once an element clicked, use the [up-dismiss] attribute in the same fashion.

Closing by targeting the parent layer

When a link or form targets a parent layer, the current layer will dismiss when the parent layer is updated. This behavior is called peeling.

The example below uses an [up-layer] attribute to update the parent layer after a successful form submission:

<form method="post" action="/users" up-layer="parent">
  <input type="text" name="email">
  <button type="submit">Create user</button>
</form>

A successful submission will now dismiss the form's own overlay with a dismissal value of ":peel".

Note

The form will still update its own layer when the server responds with an error code due to a validation error. To target another layer in this case, set an [up-fail-layer] attribute.

Customizing dismiss controls

By default the user can dismiss an overlay user by pressing Escape, by clicking outside the overlay box or by pressing an × icon in the top-right corner.

You may customize the dismiss methods available to the user by passing a { dismissable } option or [up-dismissable] attribute when opening an overlay.

The option value should contain the name of one or more dismiss controls:

Method Effect Dismiss value
key Enables dimissing with Escape key ":key"
outside Enables dismissing by clicking on the background ":outside"
button Adds an × button to the layer ":button"

Regardless of what is configured here, an overlay may always be dismissed by using the up.layer.dismiss() method or [up-dismiss] attribute.

Close animation

The overlay element's disappeared will be animated using the layer mode's configured { closeAnimation }.

To use a different animation, pass an { animation } option to the various close methods.

Using overlays as promises

Instead of using up.layer.open() and passing callbacks, you may use up.layer.ask(). up.layer.ask() returns a promise for the acceptance value, which you can await:

let user = await up.layer.ask({ url: '/users/new' })
console.log('Got user:', user)

If the overlay is dismissed instead of accepted, the promise will be rejected with the dismissal value:

try {
  await up.layer.ask({ url: '/users/new' })
} catch (reason) {
  console.log('Overlay was dismissed:', reason)
}