You rarely need to change server-side code in order to use Unpoly. There is no need to provide a JSON API, or add extra routes for AJAX requests. The server simply renders a series of full HTML pages, just like it would without Unpoly.
That said, there is an optional protocol your server can use to exchange additional information when Unpoly is updating fragments.
While the protocol can help you optimize performance and handle some
edge cases, implementing it is entirely optional. For instance,
unpoly.com itself is a static site that uses Unpoly on the frontend
and doesn't even have a server component.
If you have installed Unpoly as a Rails gem, the protocol is already implemented and you will get some Ruby bindings in your controllers and views. If your server-side app uses another language or framework, you should be able to implement the protocol in a very short time.
Unpoly requires an additional response header to detect redirects, which are otherwise undetectable for any AJAX client.
After the form's action performs a redirect, the next response should include the new URL in the HTTP headers:
The simplest implementation is to set these headers for every request.
When updating a fragment, Unpoly will send an additional HTTP header containing the CSS selector that is being replaced:
Server-side code is free to optimize its response by only returning HTML that matches the selector. For example, you might prefer to not render an expensive sidebar if the sidebar is not targeted.
Unpoly will often update a different selector in case the request fails. This selector is also included as a HTTP header:
When updating a fragment, Unpoly will by default
<title> from the server response and update the document title accordingly.
The server can also force Unpoly to set a document title by passing a HTTP header:
X-Up-Title: My server-pushed title
This is useful when you optimize your response and not render
the application layout unless it is targeted. Since your optimized response
no longer includes a
<title>, you can instead use the HTTP header to pass the document title.
When submitting a form via AJAX
Unpoly needs to know whether the form submission has failed (to update the form with
validation errors) or succeeded (to update the
For Unpoly to be able to detect a failed form submission, the response must be return a non-200 HTTP status code. We recommend to use either 400 (bad request) or 422 (unprocessable entity).
class UsersController < ApplicationController def create user_params = params[:user].permit(:email, :password) @user = User.new(user_params) if @user.save? sign_in @user else render 'form', status: :bad_request end end end
When validating a form, Unpoly will send an additional HTTP header containing a CSS selector for the form that is being updated:
When detecting a validation request, the server is expected to validate (but not save) the form submission and render a new copy of the form with validation errors.
Below you will an example for a writing route that is aware of Unpoly's live form validations. The code is for Ruby on Rails, but you can adapt it for other languages:
class UsersController < ApplicationController def create user_params = params[:user].permit(:email, :password) @user = User.new(user_params) if request.headers['X-Up-Validate'] @user.valid? # run validations, but don't save to the database render 'form' # render form with error messages elsif @user.save? sign_in @user else render 'form', status: :bad_request end end end
If the initial page was loaded with a non-
GET HTTP method, Unpoly prefers to make a full
page load when you try to update a fragment. Once the next page was loaded with a
Unpoly will restore its standard behavior.
This fixes two edge cases you might or might not care about:
In order to allow Unpoly to detect the HTTP method of the initial page load, the server must set a cookie:
When Unpoly boots, it will look for this cookie and configure its behavior accordingly. The cookie is then deleted in order to not affect following requests.
The simplest implementation is to set this cookie for every request that is neither
GET nor contains an
X-Up-Target header. For all other requests
an existing cookie should be deleted.