This page outlines ways to target and update fragments on your page.
Unpoly uses CSS selectors like .content
to match an element in the current page and server response:
<a href="/posts/5" up-target=".content">Read post</a>
<div class=".content">
Post will appear here!
</div>
<div class=".other">
This fragment will not change.
</div>
The server response is expected to include a <div class=".content">
element. The response may include other HTML (even an entire HTML document), but only the element matching .content
will be updated on the page. Other elements from the response will be discarded.
In JavaScript API, many Unpoly functions take an { target }
option to indicate what fragment should be updated:
up.render({ target: '.content', url: '/posts/5' })
You can update multiple fragments from a single request by separating selectors with a comma.
For instance, if opening a post should also update a bubble showing the number of unread posts, you might do this:
<a href="/posts/5" up-target=".content, .unread-count">Read post</a>
When one of your target elements is an ancestor of another target, Unpoly will only request the ancestor.
For instance, the following link would only target body
, since that
already contains .unread-count
:
<a href="/home" up-target="body, .unread-count">...</a>
By default Unpoly expects all targeted fragments to be present in
both the current page and the server response.
If a target selector doesn't match in either, an error up.CannotMatch
is thrown.
You may mark a target as optional by using the :maybe
pseudo selector.
<a href="/card/5" up-target=".content, .unread-count:maybe">...</a>
In this case Unpoly would only require .content
to match. If .unread-count
is missing
in the current page or the server response, Unpoly will only update .content
without an error.
Instead of including an optional target in your target selector, you can also set an [up-hungry]
attribute on the element that should optionally be updated. A target derived from the [up-hungry]
element will be included whenever a matching element will be found in the server response:
<div class=".unread-count" up-hungry>12</div>
<!-- Following this link will update .content, .unread-count:maybe -->
<a href="/card/5" up-target=".content">...</a>
Common use cases for [up-hungry]
are unread message counters or page-specific subnavigation bars. Such elements often live in the application layout, outside of the fragment that is being targeted.
Many links and forms update the site's primary content area, called the main element. You can target the main element using the :main
selector, or by omitting an target entirely:
<a href="/cards/5" up-follow>Load post</a>
<main>
Post will appear heare
</main>
Similarily when a JavaScript function omits an { target }
option, the main target will be rendered:
up.render({ url: '/cards/5' })
You may configure main target selectors in up.fragment.config.mainTargets
.
When you pass an Element
object to a rendering function, Unpoly will derive a selector that will match the element:
let element = document.querySelector('#foo')
up.reload(element) // Derives the target '#foo' from the given element
See target derivation for more details and examples.
Instead of swapping an entire fragment you may append children to an existing fragment by using the
:after
pseudo selector. In the same fashion, you can use :before
to prepend the loaded content.
A practical example would be a paginated list of items. Below the list is
a button to load the next page. You can append to the existing list
by using :after
in the [up-target]
selector like this:
<ul class="tasks">
<li>Wash car</li>
<li>Purchase supplies</li>
<li>Fix tent</li>
</ul>
<a href="/page/2" class="next-page" up-target=".tasks:after, .next-page">
Load more tasks
</a>
The server is still expected to render an entire <ul class="tasks">
, but only its <li>
children are used to extend the existing list.
If you would like to preserve the target element, but replace all of its child content,
use the :content
pseudo selector:
<div class="card">...</div>
<a href="/cards/5" up-target=".card:content">Show card #5</a>
The server is still expected to render an element matching .card
, but only its child content is used.
For more advanced strategies for preserving elements, see [up-keep]
.
To make a server request without changing a fragment, target the :none
selector:
<a href="/ping" up-target=":none">Ping server</a>
Tip
Event when a specific target like
.content
is used, the server can still decide to render nothing.
Sometimes there are multiple components with the same selector on the page:
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
While you can set [id]
attributes to uniquely identify an element on the page, this may not be necessary. When an ambiguous selector like .card
matches more than one element, Unpoly will prefer to match a fragment near the a link or form that the user interacted with (the origin). If there is no match in close proximity, Unpoly will update the first match in the layer.
When rendering programmatically, you can pass the interaction's origin element as { origin }
option.
Below you can find examples of targeting elements with ambiguous selectors, which Unpoly resolves by proximity to the interaction origin. A more elaborate example is the Tasks list of the Unpoly Demo App.
Assume we have two links that replace .card
:
<div class="card">
Card #1 preview
<a href="/cards/1" up-target=".card">Show full card #1</a>
</div>
<div class="card">
Card #2 preview
<a href="/cards/2" up-target=".card">Show full card #2</a>
</div>
When clicking on "Show full card #2", Unpoly will replace the second card, since that is a matching ancestor of the link followed.
The interaction origin can only be considered in the current page, but not in the server response. In the example above the server is expected to only render a single .card
element.
<div class="card">
<div class=".card-text">Card #1 preview</div>
<a href="/cards/1" up-target=".card .card-text">Show full card #1</a>
</div>
<div class="card">
<div class="card-text">Card #2 preview</div>
<a href="/cards/2" up-target=".card .card-text">Show full card #2</a>
</div>
When clicking on "Show full card #2", Unpoly will replace the .card-text
within the second card.
The interaction origin can only be considered in the current page, but not in the server response. In the example above the server is expected to only render a single .card
element.
If matching fragments around the origin does not work for you, you can tell Unpoly to use the fragment matching the target selector:
{ match: 'first' }
option to a function that matches a fragment.[up-match=first]
option on a link or form that matches a fragment.up.fragment.config.match = 'first
to disable region-aware fragment matching for all functions and elements. You can then opt in again with { match: 'region' }
or [up-link=region]
.By default Unpoly requires targets to match in both the current page and the server response. If no matching element is found in either, an error up.CannotMatch
will be thrown.
Instead of failing with an error you may also configure a fallback target that will be used if the primary target cannot be matched.
A fallback target is a CSS selector in an [up-fallback]
attribute or { fallback }
option:
up.render({ url: '/path', target: '.content', fallback: 'body' })
If no element matches .content
in either page or response, Unpoly will update the body
element.
If neither primary nor fallback targets can be matched, the render pass will fail with an error up.CannotMatch
.
It is often useful to render the main target when the primary target cannot be matched. The assumption here is that missing targets are often caused by the server rendering an error message. Falling back to the main target will display the error message, instead of the link or form failing silently.
To fallback to the main target you can set an empty [up-fallback]
attribute or pass an { fallback: true }
option:
up.render({ url: '/path', target: '.content', fallback: true })
Falling back to the main target is the default when navigating. Therefore you don't need to include an empty [up-fallback]
attribute with your links and forms, which are considered navigation by default.
The server may elect to render a different target by seting an X-Up-Target
response header.
Events like up:link:follow
, up:form:submit
and up:fragment:loaded
also let you change the target
by setting event.renderOptions.target
to a new selector.
See Changing options before rendering for an example.