###* Server protocol =============== 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](/up.link). 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](/install/rails), the protocol is already implemented and you will get some [Ruby bindings](https://github.com/unpoly/unpoly/blob/master/README_RAILS.md) 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. \#\#\# Redirect detection 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 this HTTP header: ```http X-Up-Location: /current-url ``` The **simplest implementation** is to set this header for every request. \#\#\# Optimizing responses When [updating a fragment](http://unpoly.com/up.link), Unpoly will send an additional HTTP header containing the CSS selector that is being replaced: ```http X-Up-Target: .user-list ``` 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. \#\#\# Pushing a document title to the client When [updating a fragment](http://unpoly.com/up.link), Unpoly will by default extract the `` 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: ```http X-Up-Title: My server-pushed title ``` This is useful when you [optimize your response](#optimizing-responses) 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. \#\#\# Signaling failed form submissions When [submitting a form via AJAX](http://unpoly.com/form-up-target) Unpoly needs to know whether the form submission has failed (to update the form with validation errors) or succeeded (to update the `up-target` selector). 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). To do so in [Ruby on Rails](http://rubyonrails.org/), pass a [`:status` option to `render`](http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option): 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 \#\#\# Detecting live form validations When [validating a form](http://unpoly.com/up-validate), Unpoly will send an additional HTTP header containing a CSS selector for the form that is being updated: ```http X-Up-Validate: .user-form ``` 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](http://rubyonrails.org/), 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 \#\#\# Signaling the initial request method This is a edge case you might or might not care about: 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 a page was loaded with a `GET` method, Unpoly will restore its standard behavior. The reason for this is that some browsers remember the method of the initial page load and don't let the application change it, even with `pushState`. Thus, when the user reloads the page much later, an affected browser might request a `POST`, `PUT`, etc. instead of the correct method. In order to allow Unpoly to detect the HTTP method of the initial page load, the server must set a cookie: ```http Set-Cookie: _up_method=POST ``` 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](/#optimizing-responses). For all other requests an existing cookie should be deleted. @class up.protocol ### up.protocol = (($) -> u = up.util ###* @function up.protocol.locationFromXhr @internal ### locationFromXhr = (xhr) -> xhr.getResponseHeader(config.locationHeader) ###* @function up.protocol.titleFromXhr @internal ### titleFromXhr = (xhr) -> xhr.getResponseHeader(config.titleHeader) ###* @function up.protocol.methodFromXhr @internal ### methodFromXhr = (xhr) -> xhr.getResponseHeader(config.methodHeader) ###* Server-side companion libraries like unpoly-rails set this cookie so we have a way to detect the request method of the initial page load. There is no JavaScript API for this. @function up.protocol.initialRequestMethod @internal ### initialRequestMethod = u.memoize -> methodFromServer = up.browser.popCookie(config.methodCookie) (methodFromServer || 'get').toLowerCase() ###* Configures strings used in the optional [server protocol](/up.protocol). @property up.protocol.config @param [config.targetHeader='X-Up-Target'] @param [config.locationHeader='X-Up-Location'] @param [config.titleHeader='X-Up-Title'] @param [config.validateHeader='X-Up-Validate'] @param [config.methodHeader='X-Up-Method'] @param [config.methodCookie='_up_method'] @param [config.methodParam='_method'] The name of the POST parameter when [wrapping HTTP methods](/up.form.config#config.wrapMethods) in a `POST` request. @experimental ### config = u.config targetHeader: 'X-Up-Target' locationHeader: 'X-Up-Location' validateHeader: 'X-Up-Validate' titleHeader: 'X-Up-Title' methodHeader: 'X-Up-Method' methodCookie: '_up_method' methodParam: '_method' ## Unfortunately we cannot offer reset without introducing cycles ## in the asset load order # # reset = -> # config.reset() # # up.on 'up:framework:reset', reset config: config locationFromXhr: locationFromXhr titleFromXhr: titleFromXhr methodFromXhr: methodFromXhr initialRequestMethod: initialRequestMethod )(jQuery)