Learn Targeting other layers

Links, forms and JavaScript functions work within their own layer by default. An [up-layer] attribute or { layer } option lets you address another layer instead, like the page behind an overlay or the root layer.

Layer isolation

Layers are isolated from each other. Without an [up-layer] attribute, a link or form only updates its own layer.

Say an overlay and the page behind it both show a .user-list. This link inside the overlay updates the overlay's .user-list:

<a href="/users" up-target=".user-list">Reload users</a>

The .user-list in the page behind the overlay stays unchanged, even though it matches the selector, too.

Lookup functions work the same way. up.fragment.get() and up.fragment.all() only see elements in the current layer:

up.fragment.get('.user-list') // result: the .user-list in the current layer

Updating another layer

A link inside an overlay updates that overlay. To update the layer behind it instead, set an [up-layer] attribute:

<a href="/users" up-layer="parent" up-target=".user-list">Show all users</a> <!-- mark: up-layer="parent" -->

Unpoly fetches /users and swaps the .user-list element in the parent layer. Because the overlay now obstructs the updated content, it is closed when the parent layer is updated. This is called peeling. To keep the overlay open, set an [up-peel="false"] attribute.

Forms work the same way. This form in an overlay creates a user, then updates the root layer:

<form method="post" action="/users" up-layer="root" up-target=".user-list"> <!-- mark: up-layer="root" -->
  ...
</form>

When rendering from JavaScript, pass a { layer } option:

up.render({ url: '/users', target: '.user-list', layer: 'parent' }) // mark: layer: 'parent'

An [up-layer] attribute already implies [up-follow] or [up-submit], so you don't need to set both.

Layer names

The attribute or option accepts these names. Most are relative to the current layer, which is usually the layer of the link or form:

Name Matches
current The current layer. This is the default.
parent The layer that opened the current layer
root The root layer, which holds the initial page
front The frontmost layer, which directly faces the user
closest The current layer or any ancestor, preferring closer layers
ancestor Any ancestor of the current layer, preferring closer layers
child The overlay opened from the current layer
descendant Any overlay opened from the current layer, directly or indirectly
subtree The current layer and its descendants
overlay Any overlay, preferring layers closer to the front
any Any layer, preferring the current layer, then layers closer to the front
origin The layer of the element passed as { origin }
0, 1, … The layer at that index in the layer stack, 0 being the root layer
new Opens a new overlay instead of updating an existing layer

Passing any disables layer isolation for that call.

To replace existing overlays instead of stacking a new one on top, use swap or shatter. See replacing existing overlays.

The current layer

When no layer is given, Unpoly matches and renders in the layer the interaction started in:

  • A link or form updates its own layer. Clicking an Unpoly-enabled link or submitting a form sets that element as the { origin }, and the origin's layer is used.
  • When an Element is passed as { target } or { origin } to a JavaScript function, that element's layer is used.
  • Otherwise up.layer.current is used.

While a compiler runs, up.layer.current is the layer of the compiled element, even when that layer is in the background. This only holds for code that runs synchronously when the compiler is called. It does not hold for code that runs later:

  • event listeners added by the compiler
  • setTimeout() callbacks
  • code after an await

In such code, pass the element as { layer }.

Current layer vs. frontmost layer

Most of the time, the current layer (up.layer.current) and the frontmost layer (up.layer.front) are the same: the last layer in the stack. There are cases where the current layer is a layer in the background:

  • While an element in a background layer is being compiled.
  • While an Unpoly event like up:request:loaded is being emitted from a background layer.
  • While an event listener bound to a background layer with up.Layer#on() is being called.

In these cases { layer: 'current' } and relative names like parent resolve from that background layer, which is what you usually want. To address the layer facing the user regardless, pass { layer: 'front' }. To temporarily change the current layer in your own code, use up.Layer#asCurrent().

Trying multiple layers

You can list several layer names, separated by a space or comma. Unpoly tries them in the given order:

<a href="/users" up-layer="parent, root" up-target=".user-list">Show all users</a> <!-- mark: up-layer="parent, root" -->

Names that match no layer are skipped. Clicked in the root layer, the link above has no parent layer, so it updates the root layer.

When rendering, the target is sought in each listed layer, and the first layer containing it is updated. From JavaScript, you can also pass an array:

up.render({ url: '/users', target: '.user-list', layer: ['parent', 'root'] })

Passing a layer object or element

Functions like up.layer.get() return an up.Layer object, and up.layer.current or up.layer.root are such objects, too. Any of them can be passed as a { layer } option.

You can also pass an Element as { layer }. Unpoly then uses the layer that contains the element.

This is useful in async code, where the current layer may have changed by the time your callback runs. A compiler already has its element, so it can pass the element instead of remembering the layer:

up.compiler('.user-list', function(element) {
  setTimeout(function() {
    // Ten seconds later, up.layer.current may be a different layer.
    let headline = up.fragment.get('h1', { layer: element }) // mark: { layer: element }
    console.log('Headline is', headline)
  }, 10000)
})

Looking up elements in another layer

Lookup functions like up.fragment.get() and up.fragment.all() only see the current layer by default. To match elsewhere, pass the same { layer } option:

up.fragment.get('.user-list', { layer: 'root' }) // mark: layer: 'root'
up.fragment.all('.flash', { layer: 'any' })      // mark: layer: 'any'

To get the up.Layer object for a layer name, use up.layer.get(). When a name matches several layers, up.layer.getAll() returns them all:

up.layer.get('parent')      // result: the parent layer, or undefined on the root layer
up.layer.getAll('ancestor') // result: all ancestors, closest first

Other features that take a layer option include:

  • A navigation bar with an [up-layer] attribute highlights links that point to another layer's location.
  • An [up-hungry] element with an [up-if-layer] attribute is updated by responses that were rendered into other layers.
  • up.fragment.abort() can abort the requests of a given layer.

Rendering error responses in another layer

Like most render options, [up-layer] only applies when the server responds successfully. When the server responds with an error code, like a form with validation errors, the response is rendered in the link's or form's own layer.

To render error responses in another layer, set an [up-fail-layer] attribute or pass a { failLayer } option.