Every link, form, overlay or poll that Unpoly handles ends in the same render pass, and every render pass takes the same options. This chapter covers those options: which fragments are updated, what else happens on a navigation, what to do when the server reports an error, and how your code can hook into the process.
When the user clicks this link, Unpoly fetches /posts/5 and swaps the .post element
with its counterpart from the response:
<a href="/posts/5" up-target=".post">Read post</a> <!-- mark: up-target=".post" -->
Internally the link becomes a call to up.render(). Your own code can make that call directly:
up.render({ target: '.post', url: '/posts/5' })
The target is a CSS selector that must match in both the current page and the server response.
The link itself is the origin of the update: the element the user interacted with.
Everything else that a render pass does, from caching to scrolling, is a render option.
An option is available in three forms: as an [up-*] attribute on a link or form,
as a camel-cased option for a function like up.render(), and as a global default in a config object.
A target can express more than one element. Separate selectors with a comma to update
several fragments from one request, and suffix a selector with :after to append to
a fragment instead of replacing it:
<ul class="tasks">
<li>Wash car</li>
<li>Fix tent</li>
</ul>
<a href="/tasks?page=2" class="next-page" up-target=".tasks:after, .next-page"> <!-- mark: .tasks:after, .next-page -->
Load more tasks
</a>
Clicking the link appends the next page's <li> children to the list and replaces the link.
When a selector like .card matches several elements, Unpoly prefers the match closest
to the origin, so a link inside the second card updates the second card.
Following a link or submitting a form is a navigation. A navigation adds a preset of render options
that make the update feel like a full page load: responses are cached, and when the main fragment changes,
the URL updates, the page scrolls to the top and focus moves to the new content.
A plain up.render() call has none of these defaults.
Each default can be turned off per element, and your JavaScript can opt in:
<a href="/posts/5" up-target=".post" up-navigate="false">Read post</a> <!-- mark: up-navigate="false" -->
up.render({ target: '.post', url: '/posts/5', navigate: true }) // mark: navigate: true
To change what navigation means for your entire app, set the defaults in
up.fragment.config.navigateOptions.
When the server responds with an error status, a different fragment often needs to change.
A failed form submission should re-render the form instead of showing the next screen.
Prefix any render option with fail to apply it only to error responses:
up.render({
url: '/users',
method: 'post',
target: '.content', // when the submission succeeds
failTarget: 'form', // mark: failTarget
})
A successful response updates .content. An error response updates the <form> instead,
so the user sees the validation errors next to their input. In HTML the same option
is [up-fail-target]. For forms, Unpoly already defaults to re-rendering the form itself.
Some elements carry state that a swap would lose, like a playing video or a map with its pan and zoom.
Mark such an element with [up-keep], and it survives when the fragment around it is replaced:
<div id="article">
<p>Article 1</p>
<audio id="player" up-keep src="song1.mp3"></audio> <!-- mark: up-keep -->
</div>
When the response contains a matching #player, Unpoly moves the existing element into the new
fragment, so the song keeps playing. You can also keep an element only until its HTML or data changes.
Rendering functions return a promise that fulfills with the inserted fragments:
let result = await up.render({ target: '.post', url: '/posts/5' })
console.log('Updated fragment:', result.fragment) // mark: result.fragment
Before a loaded response is rendered, the up:fragment:loaded event lets you inspect
it and change the render options, or prevent the update altogether. When a maintenance
page should never be rendered as a fragment, make a full page load instead:
up.on('up:fragment:loaded', function(event) {
if (event.response.header('X-Maintenance')) {
event.preventDefault() // mark-line
event.request.loadPage()
}
})
When Unpoly updates an element without being given a selector, it guesses one from the element's
[id], [name] or classes. To tune that guess, see Target derivation.
To render HTML that you already have, without a request to the server, see Providing HTML to render.
Tip Your coding agent can search these docs locally with the Unpoly agent skill