unpoly-rails 0.53.0 → 0.53.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of unpoly-rails might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +21 -2
- data/dist/unpoly.js +516 -505
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly.coffee +1 -1
- data/lib/assets/javascripts/unpoly/classes/request.coffee +4 -0
- data/lib/assets/javascripts/unpoly/dom.coffee +8 -6
- data/lib/assets/javascripts/unpoly/motion.coffee +10 -4
- data/lib/assets/javascripts/unpoly/protocol.coffee +5 -0
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile.lock +2 -2
- data/spec_app/app/assets/images/favicon.png +0 -0
- data/spec_app/app/assets/stylesheets/integration_test.sass +1 -0
- data/spec_app/app/controllers/motion_test_controller.rb +5 -0
- data/spec_app/app/controllers/reveal_test_controller.rb +5 -0
- data/spec_app/app/views/error_test/trigger.erb +10 -2
- data/spec_app/app/views/layouts/integration_test.erb +1 -0
- data/spec_app/app/views/motion_test/animations.erb +16 -0
- data/spec_app/app/views/pages/start.erb +2 -0
- data/spec_app/app/views/reveal_test/long1.erb +12 -0
- data/spec_app/app/views/reveal_test/long2.erb +12 -0
- data/spec_app/config/routes.rb +2 -0
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +12 -6
- data/spec_app/spec/javascripts/up/dom_spec.js.coffee +11 -3
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +27 -2
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f2684c206748c31c831f0891eafffb27bfaab7f995bec6fe97add98989f2cdac
|
4
|
+
data.tar.gz: 37a9f61758d26c768b7b84f17b42df9029105555e3216e74b34b8749d71ca897
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ce730da5923ec604996a319f66dbc1d5fe4f86fa7c0287794ace4f0639941bf7f12736e6b76080ed9053eef7e3fdfbd068c8f4886b44892ecd3a75f6875d161
|
7
|
+
data.tar.gz: a6067a09b1c9234e1a1bdcff034e25881da221ba96c81dbfccf2ed1ee2807ceb60c607d4582b59449ffd2866bd81af89a962791682eaea8a6d54a8750f7c366c
|
data/CHANGELOG.md
CHANGED
@@ -6,9 +6,28 @@ Changes to this project will be documented in this file.
|
|
6
6
|
This project mostly adheres to [Semantic Versioning](http://semver.org/).
|
7
7
|
|
8
8
|
|
9
|
+
0.53.1
|
10
|
+
------
|
11
|
+
|
12
|
+
### General
|
13
|
+
|
14
|
+
- Fix a bug where replacing the first element on the page (in DOM order) would shift the scroll position if animation is disabled.
|
15
|
+
- Fix a bug where query params would be lost when Unpoly would fall back to a full page load.
|
16
|
+
|
17
|
+
### Optional server protocol
|
18
|
+
|
19
|
+
- The optional cookie the server can send to [signal the initial request method](/up.protocol#signaling-the-initial-request-method) will now be removed as soon as Unpoly has booted.
|
9
20
|
|
10
|
-
|
11
|
-
|
21
|
+
### Animations
|
22
|
+
|
23
|
+
- Fix a bug where the animation `move-from-top` would finish instantly after animating with `move-to-top`.
|
24
|
+
- Fix a bug where the animation `move-from-right` would finish instantly after animating with `move-to-right`.
|
25
|
+
- Fix a bug where the animation `move-from-bottom` would finish instantly after animating with `move-to-bottom`.
|
26
|
+
- Fix a bug where the animation `move-from-left` wwould finish instantly after animating with `move-to-left`.
|
27
|
+
|
28
|
+
|
29
|
+
0.53.0
|
30
|
+
------
|
12
31
|
|
13
32
|
### New module: Passive updates
|
14
33
|
|
data/dist/unpoly.js
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
(function() {
|
7
7
|
window.up = {
|
8
|
-
version: "0.53.
|
8
|
+
version: "0.53.1",
|
9
9
|
renamedModule: function(oldName, newName) {
|
10
10
|
return typeof Object.defineProperty === "function" ? Object.defineProperty(up, oldName, {
|
11
11
|
get: function() {
|
@@ -2100,7 +2100,7 @@ that might save you from loading something like [Lodash](https://lodash.com/).
|
|
2100
2100
|
@internal
|
2101
2101
|
*/
|
2102
2102
|
rejectOnError = function(block) {
|
2103
|
-
var error;
|
2103
|
+
var error, error1;
|
2104
2104
|
try {
|
2105
2105
|
return block();
|
2106
2106
|
} catch (error1) {
|
@@ -3271,6 +3271,7 @@ that might save you from loading something like [Lodash](https://lodash.com/).
|
|
3271
3271
|
|
3272
3272
|
Request.prototype.navigate = function() {
|
3273
3273
|
var $form, addField, csrfParam, csrfToken, formMethod;
|
3274
|
+
this.transferSearchToData();
|
3274
3275
|
$form = $('<form class="up-page-loader"></form>');
|
3275
3276
|
addField = function(field) {
|
3276
3277
|
return $('<input type="hidden">').attr(field).appendTo($form);
|
@@ -3515,379 +3516,70 @@ that might save you from loading something like [Lodash](https://lodash.com/).
|
|
3515
3516
|
}).call(this);
|
3516
3517
|
|
3517
3518
|
/**
|
3518
|
-
|
3519
|
+
Browser support
|
3519
3520
|
===============
|
3520
3521
|
|
3521
|
-
|
3522
|
-
in order to use Unpoly. There is no need to provide a JSON API, or add
|
3523
|
-
extra routes for AJAX requests. The server simply renders a series
|
3524
|
-
of full HTML pages, just like it would without Unpoly.
|
3525
|
-
|
3526
|
-
That said, there is an **optional** protocol your server can use to
|
3527
|
-
exchange additional information when Unpoly is [updating fragments](/up.link).
|
3528
|
-
|
3529
|
-
While the protocol can help you optimize performance and handle some
|
3530
|
-
edge cases, implementing it is **entirely optional**. For instance,
|
3531
|
-
`unpoly.com` itself is a static site that uses Unpoly on the frontend
|
3532
|
-
and doesn't even have a server component.
|
3533
|
-
|
3534
|
-
## Existing implementations
|
3535
|
-
|
3536
|
-
You should be able to implement the protocol in a very short time.
|
3537
|
-
There are existing implementations for various web frameworks:
|
3538
|
-
|
3539
|
-
- [Ruby on Rails](/install/rails)
|
3540
|
-
- [Roda](https://github.com/adam12/roda-unpoly)
|
3541
|
-
- [Rack](https://github.com/adam12/rack-unpoly) (Sinatra, Padrino, Hanami, Cuba, ...)
|
3542
|
-
- [Phoenix](https://elixirforum.com/t/unpoly-a-framework-like-turbolinks/3614/15) (Elixir)
|
3543
|
-
|
3544
|
-
|
3545
|
-
## Protocol details
|
3546
|
-
|
3547
|
-
\#\#\# Redirect detection for IE11
|
3548
|
-
|
3549
|
-
On Internet Explorer 11, Unpoly cannot detect the final URL after a redirect.
|
3550
|
-
You can fix this edge case by delivering an additional HTTP header
|
3551
|
-
with the *last* response in a series of redirects:
|
3552
|
-
|
3553
|
-
```http
|
3554
|
-
X-Up-Location: /current-url
|
3555
|
-
```
|
3556
|
-
|
3557
|
-
The **simplest implementation** is to set these headers for every request.
|
3558
|
-
|
3559
|
-
|
3560
|
-
\#\#\# Optimizing responses
|
3561
|
-
|
3562
|
-
When [updating a fragment](/up.link), Unpoly will send an
|
3563
|
-
additional HTTP header containing the CSS selector that is being replaced:
|
3564
|
-
|
3565
|
-
```http
|
3566
|
-
X-Up-Target: .user-list
|
3567
|
-
```
|
3568
|
-
|
3569
|
-
Server-side code is free to **optimize its response** by only returning HTML
|
3570
|
-
that matches the selector. For example, you might prefer to not render an
|
3571
|
-
expensive sidebar if the sidebar is not targeted.
|
3572
|
-
|
3573
|
-
Unpoly will often update a different selector in case the request fails.
|
3574
|
-
This selector is also included as a HTTP header:
|
3575
|
-
|
3576
|
-
```
|
3577
|
-
X-Up-Fail-Target: body
|
3578
|
-
```
|
3579
|
-
|
3580
|
-
|
3581
|
-
\#\#\# Pushing a document title to the client
|
3582
|
-
|
3583
|
-
When [updating a fragment](/up.link), Unpoly will by default
|
3584
|
-
extract the `<title>` from the server response and update the document title accordingly.
|
3585
|
-
|
3586
|
-
The server can also force Unpoly to set a document title by passing a HTTP header:
|
3587
|
-
|
3588
|
-
```http
|
3589
|
-
X-Up-Title: My server-pushed title
|
3590
|
-
```
|
3591
|
-
|
3592
|
-
This is useful when you [optimize your response](#optimizing-responses) and not render
|
3593
|
-
the application layout unless it is targeted. Since your optimized response
|
3594
|
-
no longer includes a `<title>`, you can instead use the HTTP header to pass the document title.
|
3595
|
-
|
3596
|
-
|
3597
|
-
\#\#\# Signaling failed form submissions
|
3598
|
-
|
3599
|
-
When [submitting a form via AJAX](/form-up-target)
|
3600
|
-
Unpoly needs to know whether the form submission has failed (to update the form with
|
3601
|
-
validation errors) or succeeded (to update the `up-target` selector).
|
3602
|
-
|
3603
|
-
For Unpoly to be able to detect a failed form submission, the response must be
|
3604
|
-
return a non-200 HTTP status code. We recommend to use either
|
3605
|
-
400 (bad request) or 422 (unprocessable entity).
|
3606
|
-
|
3607
|
-
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):
|
3608
|
-
|
3609
|
-
class UsersController < ApplicationController
|
3610
|
-
|
3611
|
-
def create
|
3612
|
-
user_params = params[:user].permit(:email, :password)
|
3613
|
-
@user = User.new(user_params)
|
3614
|
-
if @user.save?
|
3615
|
-
sign_in @user
|
3616
|
-
else
|
3617
|
-
render 'form', status: :bad_request
|
3618
|
-
end
|
3619
|
-
end
|
3620
|
-
|
3621
|
-
end
|
3622
|
-
|
3623
|
-
|
3624
|
-
\#\#\# Detecting live form validations
|
3625
|
-
|
3626
|
-
When [validating a form](/input-up-validate), Unpoly will
|
3627
|
-
send an additional HTTP header containing a CSS selector for the form that is
|
3628
|
-
being updated:
|
3629
|
-
|
3630
|
-
```http
|
3631
|
-
X-Up-Validate: .user-form
|
3632
|
-
```
|
3633
|
-
|
3634
|
-
When detecting a validation request, the server is expected to **validate (but not save)**
|
3635
|
-
the form submission and render a new copy of the form with validation errors.
|
3636
|
-
|
3637
|
-
Below you will an example for a writing route that is aware of Unpoly's live form
|
3638
|
-
validations. The code is for [Ruby on Rails](http://rubyonrails.org/),
|
3639
|
-
but you can adapt it for other languages:
|
3640
|
-
|
3641
|
-
class UsersController < ApplicationController
|
3642
|
-
|
3643
|
-
def create
|
3644
|
-
user_params = params[:user].permit(:email, :password)
|
3645
|
-
@user = User.new(user_params)
|
3646
|
-
if request.headers['X-Up-Validate']
|
3647
|
-
@user.valid? # run validations, but don't save to the database
|
3648
|
-
render 'form' # render form with error messages
|
3649
|
-
elsif @user.save?
|
3650
|
-
sign_in @user
|
3651
|
-
else
|
3652
|
-
render 'form', status: :bad_request
|
3653
|
-
end
|
3654
|
-
end
|
3655
|
-
|
3656
|
-
end
|
3657
|
-
|
3658
|
-
|
3659
|
-
\#\#\# Signaling the initial request method
|
3660
|
-
|
3661
|
-
If the initial page was loaded with a non-`GET` HTTP method, Unpoly prefers to make a full
|
3662
|
-
page load when you try to update a fragment. Once the next page was loaded with a `GET` method,
|
3663
|
-
Unpoly will restore its standard behavior.
|
3664
|
-
|
3665
|
-
This fixes two edge cases you might or might not care about:
|
3666
|
-
|
3667
|
-
1. Unpoly replaces the initial page state so it can later restore it when the user
|
3668
|
-
goes back to that initial URL. However, if the initial request was a POST,
|
3669
|
-
Unpoly will wrongly assume that it can restore the state by reloading with GET.
|
3670
|
-
2. Some browsers have a bug where the initial request method is used for all
|
3671
|
-
subsequently pushed states. That means if the user reloads the page on a later
|
3672
|
-
GET state, the browser will wrongly attempt a POST request.
|
3673
|
-
This issue affects Safari 9 and 10 (last tested in 2017-08).
|
3674
|
-
Modern Firefoxes, Chromes and IE10+ don't have this behavior.
|
3675
|
-
|
3676
|
-
In order to allow Unpoly to detect the HTTP method of the initial page load,
|
3677
|
-
the server must set a cookie:
|
3678
|
-
|
3679
|
-
```http
|
3680
|
-
Set-Cookie: _up_method=POST
|
3681
|
-
```
|
3522
|
+
Unpoly supports all modern browsers.
|
3682
3523
|
|
3683
|
-
|
3684
|
-
|
3524
|
+
Chrome, Firefox, Edge, Safari
|
3525
|
+
: Full support
|
3685
3526
|
|
3686
|
-
|
3687
|
-
`
|
3688
|
-
an existing cookie should be deleted.
|
3527
|
+
Internet Explorer 11
|
3528
|
+
: Full support with a `Promise` polyfill like [es6-promise](https://github.com/stefanpenner/es6-promise) (2.4 KB).
|
3689
3529
|
|
3530
|
+
Internet Explorer 10 or lower
|
3531
|
+
: Unpoly prevents itself from booting itself, leaving you with a classic server-side application.
|
3690
3532
|
|
3691
|
-
@class up.
|
3533
|
+
@class up.browser
|
3692
3534
|
*/
|
3693
3535
|
|
3694
3536
|
(function() {
|
3695
|
-
|
3696
|
-
var config, csrfParam, csrfToken, initialRequestMethod, locationFromXhr, methodFromXhr, reset, titleFromXhr, u;
|
3697
|
-
u = up.util;
|
3537
|
+
var slice = [].slice;
|
3698
3538
|
|
3699
|
-
|
3700
|
-
|
3701
|
-
|
3702
|
-
*/
|
3703
|
-
locationFromXhr = function(xhr) {
|
3704
|
-
return xhr.getResponseHeader(config.locationHeader) || xhr.responseURL;
|
3705
|
-
};
|
3539
|
+
up.browser = (function($) {
|
3540
|
+
var CONSOLE_PLACEHOLDERS, canConsole, canCssTransition, canDOMParser, canFormData, canInputEvent, canPromise, canPushState, hash, isIE10OrWorse, isRecentJQuery, isSupported, navigate, polyfilledSessionStorage, popCookie, puts, sessionStorage, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed;
|
3541
|
+
u = up.util;
|
3706
3542
|
|
3707
3543
|
/**
|
3708
|
-
@
|
3544
|
+
@method up.browser.navigate
|
3545
|
+
@param {string} url
|
3546
|
+
@param {string} [options.method='get']
|
3547
|
+
@param {Object|Array} [options.data]
|
3709
3548
|
@internal
|
3710
3549
|
*/
|
3711
|
-
|
3712
|
-
|
3550
|
+
navigate = function(url, options) {
|
3551
|
+
var request;
|
3552
|
+
if (options == null) {
|
3553
|
+
options = {};
|
3554
|
+
}
|
3555
|
+
request = new up.Request(u.merge(options, {
|
3556
|
+
url: url
|
3557
|
+
}));
|
3558
|
+
return request.navigate();
|
3713
3559
|
};
|
3714
3560
|
|
3715
3561
|
/**
|
3716
|
-
|
3717
|
-
|
3562
|
+
For mocking in specs.
|
3563
|
+
|
3564
|
+
@method submitForm
|
3718
3565
|
*/
|
3719
|
-
|
3720
|
-
|
3721
|
-
if (method = xhr.getResponseHeader(config.methodHeader)) {
|
3722
|
-
return u.normalizeMethod(method);
|
3723
|
-
}
|
3566
|
+
submitForm = function($form) {
|
3567
|
+
return $form.submit();
|
3724
3568
|
};
|
3725
3569
|
|
3726
3570
|
/**
|
3727
|
-
|
3728
|
-
have a way to detect the request method of the initial page load.
|
3729
|
-
There is no JavaScript API for this.
|
3571
|
+
A cross-browser way to interact with `console.log`, `console.error`, etc.
|
3730
3572
|
|
3731
|
-
|
3732
|
-
|
3733
|
-
|
3734
|
-
|
3735
|
-
|
3736
|
-
|
3737
|
-
|
3738
|
-
|
3739
|
-
|
3740
|
-
|
3741
|
-
Configures strings used in the optional [server protocol](/up.protocol).
|
3742
|
-
|
3743
|
-
@property up.protocol.config
|
3744
|
-
@param {String} [config.targetHeader='X-Up-Target']
|
3745
|
-
@param {String} [config.failTargetHeader='X-Up-Fail-Target']
|
3746
|
-
@param {String} [config.locationHeader='X-Up-Location']
|
3747
|
-
@param {String} [config.titleHeader='X-Up-Title']
|
3748
|
-
@param {String} [config.validateHeader='X-Up-Validate']
|
3749
|
-
@param {String} [config.methodHeader='X-Up-Method']
|
3750
|
-
@param {String} [config.methodCookie='_up_method']
|
3751
|
-
The name of the optional cookie the server can send to
|
3752
|
-
[signal the initial request method](/up.protocol#signaling-the-initial-request-method).
|
3753
|
-
@param {String} [config.methodParam='_method']
|
3754
|
-
The name of the POST parameter when [wrapping HTTP methods](/up.form.config#config.wrapMethods)
|
3755
|
-
in a `POST` request.
|
3756
|
-
@param {String} [config.csrfHeader='X-CSRF-Token']
|
3757
|
-
The name of the HTTP header that will include the
|
3758
|
-
[CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
|
3759
|
-
for AJAX requests.
|
3760
|
-
@param {String|Function} [config.csrfParam]
|
3761
|
-
The `name` of the hidden `<input>` used for sending a
|
3762
|
-
[CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern) when
|
3763
|
-
submitting a default, non-AJAX form. For AJAX request the token is sent as an HTTP header instead.
|
3764
|
-
|
3765
|
-
The parameter name can be configured as a string or as function that returns the parameter name.
|
3766
|
-
If no name is set, no token will be sent.
|
3767
|
-
|
3768
|
-
Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
|
3769
|
-
|
3770
|
-
<meta name="csrf-param" content="authenticity_token" />
|
3771
|
-
|
3772
|
-
@param {String|Function} [config.csrfToken]
|
3773
|
-
The [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
|
3774
|
-
to send for unsafe requests. The token will be sent as either a HTTP header (for AJAX requests)
|
3775
|
-
or hidden form `<input>` (for default, non-AJAX form submissions).
|
3776
|
-
|
3777
|
-
The token can either be configured as a string or as function that returns the token.
|
3778
|
-
If no token is set, no token will be sent.
|
3779
|
-
|
3780
|
-
Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
|
3781
|
-
|
3782
|
-
<meta name='csrf-token' content='secret12345'>
|
3783
|
-
|
3784
|
-
@experimental
|
3785
|
-
*/
|
3786
|
-
config = u.config({
|
3787
|
-
targetHeader: 'X-Up-Target',
|
3788
|
-
failTargetHeader: 'X-Up-Fail-Target',
|
3789
|
-
locationHeader: 'X-Up-Location',
|
3790
|
-
validateHeader: 'X-Up-Validate',
|
3791
|
-
titleHeader: 'X-Up-Title',
|
3792
|
-
methodHeader: 'X-Up-Method',
|
3793
|
-
methodCookie: '_up_method',
|
3794
|
-
methodParam: '_method',
|
3795
|
-
csrfParam: function() {
|
3796
|
-
return $('meta[name="csrf-param"]').attr('content');
|
3797
|
-
},
|
3798
|
-
csrfToken: function() {
|
3799
|
-
return $('meta[name="csrf-token"]').attr('content');
|
3800
|
-
},
|
3801
|
-
csrfHeader: 'X-CSRF-Token'
|
3802
|
-
});
|
3803
|
-
csrfParam = function() {
|
3804
|
-
return u.evalOption(config.csrfParam);
|
3805
|
-
};
|
3806
|
-
csrfToken = function() {
|
3807
|
-
return u.evalOption(config.csrfToken);
|
3808
|
-
};
|
3809
|
-
reset = function() {
|
3810
|
-
return config.reset();
|
3811
|
-
};
|
3812
|
-
return {
|
3813
|
-
config: config,
|
3814
|
-
reset: reset,
|
3815
|
-
locationFromXhr: locationFromXhr,
|
3816
|
-
titleFromXhr: titleFromXhr,
|
3817
|
-
methodFromXhr: methodFromXhr,
|
3818
|
-
csrfParam: csrfParam,
|
3819
|
-
csrfToken: csrfToken,
|
3820
|
-
initialRequestMethod: initialRequestMethod
|
3821
|
-
};
|
3822
|
-
})(jQuery);
|
3823
|
-
|
3824
|
-
}).call(this);
|
3825
|
-
|
3826
|
-
/**
|
3827
|
-
Browser support
|
3828
|
-
===============
|
3829
|
-
|
3830
|
-
Unpoly supports all modern browsers.
|
3831
|
-
|
3832
|
-
Chrome, Firefox, Edge, Safari
|
3833
|
-
: Full support
|
3834
|
-
|
3835
|
-
Internet Explorer 11
|
3836
|
-
: Full support with a `Promise` polyfill like [es6-promise](https://github.com/stefanpenner/es6-promise) (2.4 KB).
|
3837
|
-
|
3838
|
-
Internet Explorer 10 or lower
|
3839
|
-
: Unpoly prevents itself from booting itself, leaving you with a classic server-side application.
|
3840
|
-
|
3841
|
-
@class up.browser
|
3842
|
-
*/
|
3843
|
-
|
3844
|
-
(function() {
|
3845
|
-
var slice = [].slice;
|
3846
|
-
|
3847
|
-
up.browser = (function($) {
|
3848
|
-
var CONSOLE_PLACEHOLDERS, canConsole, canCssTransition, canDOMParser, canFormData, canInputEvent, canPromise, canPushState, hash, isIE10OrWorse, isRecentJQuery, isSupported, navigate, polyfilledSessionStorage, popCookie, puts, sessionStorage, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed;
|
3849
|
-
u = up.util;
|
3850
|
-
|
3851
|
-
/**
|
3852
|
-
@method up.browser.navigate
|
3853
|
-
@param {string} url
|
3854
|
-
@param {string} [options.method='get']
|
3855
|
-
@param {Object|Array} [options.data]
|
3856
|
-
@internal
|
3857
|
-
*/
|
3858
|
-
navigate = function(url, options) {
|
3859
|
-
var request;
|
3860
|
-
if (options == null) {
|
3861
|
-
options = {};
|
3862
|
-
}
|
3863
|
-
request = new up.Request(u.merge(options, {
|
3864
|
-
url: url
|
3865
|
-
}));
|
3866
|
-
return request.navigate();
|
3867
|
-
};
|
3868
|
-
|
3869
|
-
/**
|
3870
|
-
For mocking in specs.
|
3871
|
-
|
3872
|
-
@method submitForm
|
3873
|
-
*/
|
3874
|
-
submitForm = function($form) {
|
3875
|
-
return $form.submit();
|
3876
|
-
};
|
3877
|
-
|
3878
|
-
/**
|
3879
|
-
A cross-browser way to interact with `console.log`, `console.error`, etc.
|
3880
|
-
|
3881
|
-
This function falls back to `console.log` if the output stream is not implemented.
|
3882
|
-
It also prints substitution strings (e.g. `console.log("From %o to %o", "a", "b")`)
|
3883
|
-
as a single string if the browser console does not support substitution strings.
|
3884
|
-
|
3885
|
-
\#\#\# Example
|
3886
|
-
|
3887
|
-
up.browser.puts('log', 'Hi world');
|
3888
|
-
up.browser.puts('error', 'There was an error in %o', obj);
|
3889
|
-
|
3890
|
-
@function up.browser.puts
|
3573
|
+
This function falls back to `console.log` if the output stream is not implemented.
|
3574
|
+
It also prints substitution strings (e.g. `console.log("From %o to %o", "a", "b")`)
|
3575
|
+
as a single string if the browser console does not support substitution strings.
|
3576
|
+
|
3577
|
+
\#\#\# Example
|
3578
|
+
|
3579
|
+
up.browser.puts('log', 'Hi world');
|
3580
|
+
up.browser.puts('error', 'There was an error in %o', obj);
|
3581
|
+
|
3582
|
+
@function up.browser.puts
|
3891
3583
|
@internal
|
3892
3584
|
*/
|
3893
3585
|
puts = function() {
|
@@ -4126,6 +3818,7 @@ Internet Explorer 10 or lower
|
|
4126
3818
|
@internal
|
4127
3819
|
*/
|
4128
3820
|
sessionStorage = u.memoize(function() {
|
3821
|
+
var error;
|
4129
3822
|
try {
|
4130
3823
|
return window.sessionStorage;
|
4131
3824
|
} catch (error) {
|
@@ -4566,180 +4259,490 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
|
|
4566
4259
|
};
|
4567
4260
|
|
4568
4261
|
/**
|
4569
|
-
Registers an event listener to be called when the user
|
4570
|
-
presses the `Escape` key.
|
4571
|
-
|
4572
|
-
@function up.bus.onEscape
|
4573
|
-
@param {Function} listener
|
4574
|
-
The listener function to register.
|
4575
|
-
@return {Function}
|
4576
|
-
A function that unbinds the event listeners when called.
|
4577
|
-
@experimental
|
4262
|
+
Registers an event listener to be called when the user
|
4263
|
+
presses the `Escape` key.
|
4264
|
+
|
4265
|
+
@function up.bus.onEscape
|
4266
|
+
@param {Function} listener
|
4267
|
+
The listener function to register.
|
4268
|
+
@return {Function}
|
4269
|
+
A function that unbinds the event listeners when called.
|
4270
|
+
@experimental
|
4271
|
+
*/
|
4272
|
+
onEscape = function(listener) {
|
4273
|
+
return live('keydown', 'body', function(event) {
|
4274
|
+
if (u.escapePressed(event)) {
|
4275
|
+
return listener(event);
|
4276
|
+
}
|
4277
|
+
});
|
4278
|
+
};
|
4279
|
+
|
4280
|
+
/**
|
4281
|
+
Stops the given event from propagating and prevents the default action.
|
4282
|
+
|
4283
|
+
@function up.bus.haltEvent
|
4284
|
+
@internal
|
4285
|
+
*/
|
4286
|
+
haltEvent = function(event) {
|
4287
|
+
event.stopImmediatePropagation();
|
4288
|
+
event.stopPropagation();
|
4289
|
+
return event.preventDefault();
|
4290
|
+
};
|
4291
|
+
|
4292
|
+
/**
|
4293
|
+
@function up.bus.consumeAction
|
4294
|
+
@internal
|
4295
|
+
*/
|
4296
|
+
consumeAction = function(event) {
|
4297
|
+
haltEvent(event);
|
4298
|
+
if (event.type !== 'up:action:consumed') {
|
4299
|
+
return emit('up:action:consumed', {
|
4300
|
+
$element: $(event.target),
|
4301
|
+
message: false
|
4302
|
+
});
|
4303
|
+
}
|
4304
|
+
};
|
4305
|
+
|
4306
|
+
/**
|
4307
|
+
Makes a snapshot of the currently registered event listeners,
|
4308
|
+
to later be restored through [`up.bus.reset()`](/up.bus.reset).
|
4309
|
+
|
4310
|
+
@internal
|
4311
|
+
*/
|
4312
|
+
snapshot = function() {
|
4313
|
+
var description, number, results;
|
4314
|
+
results = [];
|
4315
|
+
for (number in liveUpDescriptions) {
|
4316
|
+
description = liveUpDescriptions[number];
|
4317
|
+
results.push(description.isDefault = true);
|
4318
|
+
}
|
4319
|
+
return results;
|
4320
|
+
};
|
4321
|
+
resetBus = function() {
|
4322
|
+
var description, doomedDescriptions, i, len, number, results;
|
4323
|
+
doomedDescriptions = [];
|
4324
|
+
for (number in liveUpDescriptions) {
|
4325
|
+
description = liveUpDescriptions[number];
|
4326
|
+
if (!description.isDefault) {
|
4327
|
+
doomedDescriptions.push(description);
|
4328
|
+
}
|
4329
|
+
}
|
4330
|
+
results = [];
|
4331
|
+
for (i = 0, len = doomedDescriptions.length; i < len; i++) {
|
4332
|
+
description = doomedDescriptions[i];
|
4333
|
+
results.push(unbind.apply(null, description));
|
4334
|
+
}
|
4335
|
+
return results;
|
4336
|
+
};
|
4337
|
+
|
4338
|
+
/**
|
4339
|
+
Resets Unpoly to the state when it was booted.
|
4340
|
+
All custom event handlers, animations, etc. that have been registered
|
4341
|
+
will be discarded.
|
4342
|
+
|
4343
|
+
This is an internal method for to enable unit testing.
|
4344
|
+
Don't use this in production.
|
4345
|
+
|
4346
|
+
Emits event [`up:framework:reset`](/up:framework:reset).
|
4347
|
+
|
4348
|
+
@function up.reset
|
4349
|
+
@experimental
|
4350
|
+
*/
|
4351
|
+
emitReset = function() {
|
4352
|
+
emit('up:framework:reset', {
|
4353
|
+
message: 'Resetting framework'
|
4354
|
+
});
|
4355
|
+
return up.protocol.reset();
|
4356
|
+
};
|
4357
|
+
|
4358
|
+
/**
|
4359
|
+
This event is [emitted](/up.emit) when Unpoly is [reset](/up.reset) during unit tests.
|
4360
|
+
|
4361
|
+
@event up:framework:reset
|
4362
|
+
@experimental
|
4363
|
+
*/
|
4364
|
+
renamedEvent = function(oldEvent, newEvent) {
|
4365
|
+
return renamedEvents[oldEvent] = newEvent;
|
4366
|
+
};
|
4367
|
+
|
4368
|
+
/**
|
4369
|
+
Boots the Unpoly framework.
|
4370
|
+
|
4371
|
+
**This is called automatically** by including the Unpoly JavaScript files.
|
4372
|
+
|
4373
|
+
Unpoly will not boot if the current browser is [not supported](/up.browser.isSupported).
|
4374
|
+
This leaves you with a classic server-side application on legacy browsers.
|
4375
|
+
|
4376
|
+
@function up.boot
|
4377
|
+
@internal
|
4378
|
+
*/
|
4379
|
+
boot = function() {
|
4380
|
+
if (up.browser.isSupported()) {
|
4381
|
+
emit('up:framework:boot', {
|
4382
|
+
message: 'Booting framework'
|
4383
|
+
});
|
4384
|
+
emit('up:framework:booted', {
|
4385
|
+
message: 'Framework booted'
|
4386
|
+
});
|
4387
|
+
return u.nextFrame(function() {
|
4388
|
+
return u.whenReady().then(function() {
|
4389
|
+
emit('up:app:boot', {
|
4390
|
+
message: 'Booting user application'
|
4391
|
+
});
|
4392
|
+
return emit('up:app:booted', {
|
4393
|
+
message: 'User application booted'
|
4394
|
+
});
|
4395
|
+
});
|
4396
|
+
});
|
4397
|
+
} else {
|
4398
|
+
return typeof console.log === "function" ? console.log("Unpoly doesn't support this browser. Framework was not booted.") : void 0;
|
4399
|
+
}
|
4400
|
+
};
|
4401
|
+
|
4402
|
+
/**
|
4403
|
+
This event is [emitted](/up.emit) when Unpoly [starts to boot](/up.boot).
|
4404
|
+
|
4405
|
+
@event up:framework:boot
|
4406
|
+
@internal
|
4407
|
+
*/
|
4408
|
+
live('up:framework:booted', snapshot);
|
4409
|
+
live('up:framework:reset', resetBus);
|
4410
|
+
return {
|
4411
|
+
knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
|
4412
|
+
on: live,
|
4413
|
+
off: unbind,
|
4414
|
+
emit: emit,
|
4415
|
+
nobodyPrevents: nobodyPrevents,
|
4416
|
+
whenEmitted: whenEmitted,
|
4417
|
+
onEscape: onEscape,
|
4418
|
+
emitReset: emitReset,
|
4419
|
+
haltEvent: haltEvent,
|
4420
|
+
consumeAction: consumeAction,
|
4421
|
+
renamedEvent: renamedEvent,
|
4422
|
+
boot: boot
|
4423
|
+
};
|
4424
|
+
})(jQuery);
|
4425
|
+
|
4426
|
+
up.on = up.bus.on;
|
4427
|
+
|
4428
|
+
up.off = up.bus.off;
|
4429
|
+
|
4430
|
+
up.emit = up.bus.emit;
|
4431
|
+
|
4432
|
+
up.reset = up.bus.emitReset;
|
4433
|
+
|
4434
|
+
up.boot = up.bus.boot;
|
4435
|
+
|
4436
|
+
}).call(this);
|
4437
|
+
|
4438
|
+
/**
|
4439
|
+
Server protocol
|
4440
|
+
===============
|
4441
|
+
|
4442
|
+
You rarely need to change server-side code
|
4443
|
+
in order to use Unpoly. There is no need to provide a JSON API, or add
|
4444
|
+
extra routes for AJAX requests. The server simply renders a series
|
4445
|
+
of full HTML pages, just like it would without Unpoly.
|
4446
|
+
|
4447
|
+
That said, there is an **optional** protocol your server can use to
|
4448
|
+
exchange additional information when Unpoly is [updating fragments](/up.link).
|
4449
|
+
|
4450
|
+
While the protocol can help you optimize performance and handle some
|
4451
|
+
edge cases, implementing it is **entirely optional**. For instance,
|
4452
|
+
`unpoly.com` itself is a static site that uses Unpoly on the frontend
|
4453
|
+
and doesn't even have a server component.
|
4454
|
+
|
4455
|
+
## Existing implementations
|
4456
|
+
|
4457
|
+
You should be able to implement the protocol in a very short time.
|
4458
|
+
There are existing implementations for various web frameworks:
|
4459
|
+
|
4460
|
+
- [Ruby on Rails](/install/rails)
|
4461
|
+
- [Roda](https://github.com/adam12/roda-unpoly)
|
4462
|
+
- [Rack](https://github.com/adam12/rack-unpoly) (Sinatra, Padrino, Hanami, Cuba, ...)
|
4463
|
+
- [Phoenix](https://elixirforum.com/t/unpoly-a-framework-like-turbolinks/3614/15) (Elixir)
|
4464
|
+
|
4465
|
+
|
4466
|
+
## Protocol details
|
4467
|
+
|
4468
|
+
\#\#\# Redirect detection for IE11
|
4469
|
+
|
4470
|
+
On Internet Explorer 11, Unpoly cannot detect the final URL after a redirect.
|
4471
|
+
You can fix this edge case by delivering an additional HTTP header
|
4472
|
+
with the *last* response in a series of redirects:
|
4473
|
+
|
4474
|
+
```http
|
4475
|
+
X-Up-Location: /current-url
|
4476
|
+
```
|
4477
|
+
|
4478
|
+
The **simplest implementation** is to set these headers for every request.
|
4479
|
+
|
4480
|
+
|
4481
|
+
\#\#\# Optimizing responses
|
4482
|
+
|
4483
|
+
When [updating a fragment](/up.link), Unpoly will send an
|
4484
|
+
additional HTTP header containing the CSS selector that is being replaced:
|
4485
|
+
|
4486
|
+
```http
|
4487
|
+
X-Up-Target: .user-list
|
4488
|
+
```
|
4489
|
+
|
4490
|
+
Server-side code is free to **optimize its response** by only returning HTML
|
4491
|
+
that matches the selector. For example, you might prefer to not render an
|
4492
|
+
expensive sidebar if the sidebar is not targeted.
|
4493
|
+
|
4494
|
+
Unpoly will often update a different selector in case the request fails.
|
4495
|
+
This selector is also included as a HTTP header:
|
4496
|
+
|
4497
|
+
```
|
4498
|
+
X-Up-Fail-Target: body
|
4499
|
+
```
|
4500
|
+
|
4501
|
+
|
4502
|
+
\#\#\# Pushing a document title to the client
|
4503
|
+
|
4504
|
+
When [updating a fragment](/up.link), Unpoly will by default
|
4505
|
+
extract the `<title>` from the server response and update the document title accordingly.
|
4506
|
+
|
4507
|
+
The server can also force Unpoly to set a document title by passing a HTTP header:
|
4508
|
+
|
4509
|
+
```http
|
4510
|
+
X-Up-Title: My server-pushed title
|
4511
|
+
```
|
4512
|
+
|
4513
|
+
This is useful when you [optimize your response](#optimizing-responses) and not render
|
4514
|
+
the application layout unless it is targeted. Since your optimized response
|
4515
|
+
no longer includes a `<title>`, you can instead use the HTTP header to pass the document title.
|
4516
|
+
|
4517
|
+
|
4518
|
+
\#\#\# Signaling failed form submissions
|
4519
|
+
|
4520
|
+
When [submitting a form via AJAX](/form-up-target)
|
4521
|
+
Unpoly needs to know whether the form submission has failed (to update the form with
|
4522
|
+
validation errors) or succeeded (to update the `up-target` selector).
|
4523
|
+
|
4524
|
+
For Unpoly to be able to detect a failed form submission, the response must be
|
4525
|
+
return a non-200 HTTP status code. We recommend to use either
|
4526
|
+
400 (bad request) or 422 (unprocessable entity).
|
4527
|
+
|
4528
|
+
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):
|
4529
|
+
|
4530
|
+
class UsersController < ApplicationController
|
4531
|
+
|
4532
|
+
def create
|
4533
|
+
user_params = params[:user].permit(:email, :password)
|
4534
|
+
@user = User.new(user_params)
|
4535
|
+
if @user.save?
|
4536
|
+
sign_in @user
|
4537
|
+
else
|
4538
|
+
render 'form', status: :bad_request
|
4539
|
+
end
|
4540
|
+
end
|
4541
|
+
|
4542
|
+
end
|
4543
|
+
|
4544
|
+
|
4545
|
+
\#\#\# Detecting live form validations
|
4546
|
+
|
4547
|
+
When [validating a form](/input-up-validate), Unpoly will
|
4548
|
+
send an additional HTTP header containing a CSS selector for the form that is
|
4549
|
+
being updated:
|
4550
|
+
|
4551
|
+
```http
|
4552
|
+
X-Up-Validate: .user-form
|
4553
|
+
```
|
4554
|
+
|
4555
|
+
When detecting a validation request, the server is expected to **validate (but not save)**
|
4556
|
+
the form submission and render a new copy of the form with validation errors.
|
4557
|
+
|
4558
|
+
Below you will an example for a writing route that is aware of Unpoly's live form
|
4559
|
+
validations. The code is for [Ruby on Rails](http://rubyonrails.org/),
|
4560
|
+
but you can adapt it for other languages:
|
4561
|
+
|
4562
|
+
class UsersController < ApplicationController
|
4563
|
+
|
4564
|
+
def create
|
4565
|
+
user_params = params[:user].permit(:email, :password)
|
4566
|
+
@user = User.new(user_params)
|
4567
|
+
if request.headers['X-Up-Validate']
|
4568
|
+
@user.valid? # run validations, but don't save to the database
|
4569
|
+
render 'form' # render form with error messages
|
4570
|
+
elsif @user.save?
|
4571
|
+
sign_in @user
|
4572
|
+
else
|
4573
|
+
render 'form', status: :bad_request
|
4574
|
+
end
|
4575
|
+
end
|
4576
|
+
|
4577
|
+
end
|
4578
|
+
|
4579
|
+
|
4580
|
+
\#\#\# Signaling the initial request method
|
4581
|
+
|
4582
|
+
If the initial page was loaded with a non-`GET` HTTP method, Unpoly prefers to make a full
|
4583
|
+
page load when you try to update a fragment. Once the next page was loaded with a `GET` method,
|
4584
|
+
Unpoly will restore its standard behavior.
|
4585
|
+
|
4586
|
+
This fixes two edge cases you might or might not care about:
|
4587
|
+
|
4588
|
+
1. Unpoly replaces the initial page state so it can later restore it when the user
|
4589
|
+
goes back to that initial URL. However, if the initial request was a POST,
|
4590
|
+
Unpoly will wrongly assume that it can restore the state by reloading with GET.
|
4591
|
+
2. Some browsers have a bug where the initial request method is used for all
|
4592
|
+
subsequently pushed states. That means if the user reloads the page on a later
|
4593
|
+
GET state, the browser will wrongly attempt a POST request.
|
4594
|
+
This issue affects Safari 9 and 10 (last tested in 2017-08).
|
4595
|
+
Modern Firefoxes, Chromes and IE10+ don't have this behavior.
|
4596
|
+
|
4597
|
+
In order to allow Unpoly to detect the HTTP method of the initial page load,
|
4598
|
+
the server must set a cookie:
|
4599
|
+
|
4600
|
+
```http
|
4601
|
+
Set-Cookie: _up_method=POST
|
4602
|
+
```
|
4603
|
+
|
4604
|
+
When Unpoly boots, it will look for this cookie and configure its behavior accordingly.
|
4605
|
+
The cookie is then deleted in order to not affect following requests.
|
4606
|
+
|
4607
|
+
The **simplest implementation** is to set this cookie for every request that is neither
|
4608
|
+
`GET` nor contains an [`X-Up-Target` header](/#optimizing-responses). For all other requests
|
4609
|
+
an existing cookie should be deleted.
|
4610
|
+
|
4611
|
+
|
4612
|
+
@class up.protocol
|
4613
|
+
*/
|
4614
|
+
|
4615
|
+
(function() {
|
4616
|
+
up.protocol = (function($) {
|
4617
|
+
var config, csrfParam, csrfToken, initialRequestMethod, locationFromXhr, methodFromXhr, reset, titleFromXhr, u;
|
4618
|
+
u = up.util;
|
4619
|
+
|
4620
|
+
/**
|
4621
|
+
@function up.protocol.locationFromXhr
|
4622
|
+
@internal
|
4578
4623
|
*/
|
4579
|
-
|
4580
|
-
return
|
4581
|
-
if (u.escapePressed(event)) {
|
4582
|
-
return listener(event);
|
4583
|
-
}
|
4584
|
-
});
|
4624
|
+
locationFromXhr = function(xhr) {
|
4625
|
+
return xhr.getResponseHeader(config.locationHeader) || xhr.responseURL;
|
4585
4626
|
};
|
4586
4627
|
|
4587
4628
|
/**
|
4588
|
-
|
4589
|
-
|
4590
|
-
@function up.bus.haltEvent
|
4629
|
+
@function up.protocol.titleFromXhr
|
4591
4630
|
@internal
|
4592
4631
|
*/
|
4593
|
-
|
4594
|
-
|
4595
|
-
event.stopPropagation();
|
4596
|
-
return event.preventDefault();
|
4632
|
+
titleFromXhr = function(xhr) {
|
4633
|
+
return xhr.getResponseHeader(config.titleHeader);
|
4597
4634
|
};
|
4598
4635
|
|
4599
4636
|
/**
|
4600
|
-
@function up.
|
4637
|
+
@function up.protocol.methodFromXhr
|
4601
4638
|
@internal
|
4602
4639
|
*/
|
4603
|
-
|
4604
|
-
|
4605
|
-
if (
|
4606
|
-
return
|
4607
|
-
$element: $(event.target),
|
4608
|
-
message: false
|
4609
|
-
});
|
4640
|
+
methodFromXhr = function(xhr) {
|
4641
|
+
var method;
|
4642
|
+
if (method = xhr.getResponseHeader(config.methodHeader)) {
|
4643
|
+
return u.normalizeMethod(method);
|
4610
4644
|
}
|
4611
4645
|
};
|
4612
4646
|
|
4613
4647
|
/**
|
4614
|
-
|
4615
|
-
to
|
4648
|
+
Server-side companion libraries like unpoly-rails set this cookie so we
|
4649
|
+
have a way to detect the request method of the initial page load.
|
4650
|
+
There is no JavaScript API for this.
|
4616
4651
|
|
4652
|
+
@function up.protocol.initialRequestMethod
|
4617
4653
|
@internal
|
4618
4654
|
*/
|
4619
|
-
|
4620
|
-
var
|
4621
|
-
|
4622
|
-
|
4623
|
-
|
4624
|
-
|
4625
|
-
}
|
4626
|
-
return results;
|
4627
|
-
};
|
4628
|
-
resetBus = function() {
|
4629
|
-
var description, doomedDescriptions, i, len, number, results;
|
4630
|
-
doomedDescriptions = [];
|
4631
|
-
for (number in liveUpDescriptions) {
|
4632
|
-
description = liveUpDescriptions[number];
|
4633
|
-
if (!description.isDefault) {
|
4634
|
-
doomedDescriptions.push(description);
|
4635
|
-
}
|
4636
|
-
}
|
4637
|
-
results = [];
|
4638
|
-
for (i = 0, len = doomedDescriptions.length; i < len; i++) {
|
4639
|
-
description = doomedDescriptions[i];
|
4640
|
-
results.push(unbind.apply(null, description));
|
4641
|
-
}
|
4642
|
-
return results;
|
4643
|
-
};
|
4655
|
+
initialRequestMethod = u.memoize(function() {
|
4656
|
+
var methodFromServer;
|
4657
|
+
methodFromServer = up.browser.popCookie(config.methodCookie);
|
4658
|
+
return (methodFromServer || 'get').toLowerCase();
|
4659
|
+
});
|
4660
|
+
up.bus.on('up:framework:booted', initialRequestMethod);
|
4644
4661
|
|
4645
4662
|
/**
|
4646
|
-
|
4647
|
-
All custom event handlers, animations, etc. that have been registered
|
4648
|
-
will be discarded.
|
4663
|
+
Configures strings used in the optional [server protocol](/up.protocol).
|
4649
4664
|
|
4650
|
-
|
4651
|
-
|
4665
|
+
@property up.protocol.config
|
4666
|
+
@param {String} [config.targetHeader='X-Up-Target']
|
4667
|
+
@param {String} [config.failTargetHeader='X-Up-Fail-Target']
|
4668
|
+
@param {String} [config.locationHeader='X-Up-Location']
|
4669
|
+
@param {String} [config.titleHeader='X-Up-Title']
|
4670
|
+
@param {String} [config.validateHeader='X-Up-Validate']
|
4671
|
+
@param {String} [config.methodHeader='X-Up-Method']
|
4672
|
+
@param {String} [config.methodCookie='_up_method']
|
4673
|
+
The name of the optional cookie the server can send to
|
4674
|
+
[signal the initial request method](/up.protocol#signaling-the-initial-request-method).
|
4675
|
+
@param {String} [config.methodParam='_method']
|
4676
|
+
The name of the POST parameter when [wrapping HTTP methods](/up.form.config#config.wrapMethods)
|
4677
|
+
in a `POST` request.
|
4678
|
+
@param {String} [config.csrfHeader='X-CSRF-Token']
|
4679
|
+
The name of the HTTP header that will include the
|
4680
|
+
[CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
|
4681
|
+
for AJAX requests.
|
4682
|
+
@param {String|Function} [config.csrfParam]
|
4683
|
+
The `name` of the hidden `<input>` used for sending a
|
4684
|
+
[CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern) when
|
4685
|
+
submitting a default, non-AJAX form. For AJAX request the token is sent as an HTTP header instead.
|
4652
4686
|
|
4653
|
-
|
4687
|
+
The parameter name can be configured as a string or as function that returns the parameter name.
|
4688
|
+
If no name is set, no token will be sent.
|
4654
4689
|
|
4655
|
-
|
4656
|
-
@experimental
|
4657
|
-
*/
|
4658
|
-
emitReset = function() {
|
4659
|
-
emit('up:framework:reset', {
|
4660
|
-
message: 'Resetting framework'
|
4661
|
-
});
|
4662
|
-
return up.protocol.reset();
|
4663
|
-
};
|
4664
|
-
|
4665
|
-
/**
|
4666
|
-
This event is [emitted](/up.emit) when Unpoly is [reset](/up.reset) during unit tests.
|
4690
|
+
Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
|
4667
4691
|
|
4668
|
-
|
4669
|
-
@experimental
|
4670
|
-
*/
|
4671
|
-
renamedEvent = function(oldEvent, newEvent) {
|
4672
|
-
return renamedEvents[oldEvent] = newEvent;
|
4673
|
-
};
|
4674
|
-
|
4675
|
-
/**
|
4676
|
-
Boots the Unpoly framework.
|
4692
|
+
<meta name="csrf-param" content="authenticity_token" />
|
4677
4693
|
|
4678
|
-
|
4694
|
+
@param {String|Function} [config.csrfToken]
|
4695
|
+
The [CSRF token](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
|
4696
|
+
to send for unsafe requests. The token will be sent as either a HTTP header (for AJAX requests)
|
4697
|
+
or hidden form `<input>` (for default, non-AJAX form submissions).
|
4679
4698
|
|
4680
|
-
|
4681
|
-
|
4699
|
+
The token can either be configured as a string or as function that returns the token.
|
4700
|
+
If no token is set, no token will be sent.
|
4682
4701
|
|
4683
|
-
|
4684
|
-
@internal
|
4685
|
-
*/
|
4686
|
-
boot = function() {
|
4687
|
-
if (up.browser.isSupported()) {
|
4688
|
-
emit('up:framework:boot', {
|
4689
|
-
message: 'Booting framework'
|
4690
|
-
});
|
4691
|
-
emit('up:framework:booted', {
|
4692
|
-
message: 'Framework booted'
|
4693
|
-
});
|
4694
|
-
return u.nextFrame(function() {
|
4695
|
-
return u.whenReady().then(function() {
|
4696
|
-
emit('up:app:boot', {
|
4697
|
-
message: 'Booting user application'
|
4698
|
-
});
|
4699
|
-
return emit('up:app:booted', {
|
4700
|
-
message: 'User application booted'
|
4701
|
-
});
|
4702
|
-
});
|
4703
|
-
});
|
4704
|
-
} else {
|
4705
|
-
return typeof console.log === "function" ? console.log("Unpoly doesn't support this browser. Framework was not booted.") : void 0;
|
4706
|
-
}
|
4707
|
-
};
|
4708
|
-
|
4709
|
-
/**
|
4710
|
-
This event is [emitted](/up.emit) when Unpoly [starts to boot](/up.boot).
|
4702
|
+
Defaults to the `content` attribute of a `<meta>` tag named `csrf-token`:
|
4711
4703
|
|
4712
|
-
|
4713
|
-
|
4704
|
+
<meta name='csrf-token' content='secret12345'>
|
4705
|
+
|
4706
|
+
@experimental
|
4714
4707
|
*/
|
4715
|
-
|
4716
|
-
|
4708
|
+
config = u.config({
|
4709
|
+
targetHeader: 'X-Up-Target',
|
4710
|
+
failTargetHeader: 'X-Up-Fail-Target',
|
4711
|
+
locationHeader: 'X-Up-Location',
|
4712
|
+
validateHeader: 'X-Up-Validate',
|
4713
|
+
titleHeader: 'X-Up-Title',
|
4714
|
+
methodHeader: 'X-Up-Method',
|
4715
|
+
methodCookie: '_up_method',
|
4716
|
+
methodParam: '_method',
|
4717
|
+
csrfParam: function() {
|
4718
|
+
return $('meta[name="csrf-param"]').attr('content');
|
4719
|
+
},
|
4720
|
+
csrfToken: function() {
|
4721
|
+
return $('meta[name="csrf-token"]').attr('content');
|
4722
|
+
},
|
4723
|
+
csrfHeader: 'X-CSRF-Token'
|
4724
|
+
});
|
4725
|
+
csrfParam = function() {
|
4726
|
+
return u.evalOption(config.csrfParam);
|
4727
|
+
};
|
4728
|
+
csrfToken = function() {
|
4729
|
+
return u.evalOption(config.csrfToken);
|
4730
|
+
};
|
4731
|
+
reset = function() {
|
4732
|
+
return config.reset();
|
4733
|
+
};
|
4717
4734
|
return {
|
4718
|
-
|
4719
|
-
|
4720
|
-
|
4721
|
-
|
4722
|
-
|
4723
|
-
|
4724
|
-
|
4725
|
-
|
4726
|
-
haltEvent: haltEvent,
|
4727
|
-
consumeAction: consumeAction,
|
4728
|
-
renamedEvent: renamedEvent,
|
4729
|
-
boot: boot
|
4735
|
+
config: config,
|
4736
|
+
reset: reset,
|
4737
|
+
locationFromXhr: locationFromXhr,
|
4738
|
+
titleFromXhr: titleFromXhr,
|
4739
|
+
methodFromXhr: methodFromXhr,
|
4740
|
+
csrfParam: csrfParam,
|
4741
|
+
csrfToken: csrfToken,
|
4742
|
+
initialRequestMethod: initialRequestMethod
|
4730
4743
|
};
|
4731
4744
|
})(jQuery);
|
4732
4745
|
|
4733
|
-
up.on = up.bus.on;
|
4734
|
-
|
4735
|
-
up.off = up.bus.off;
|
4736
|
-
|
4737
|
-
up.emit = up.bus.emit;
|
4738
|
-
|
4739
|
-
up.reset = up.bus.emitReset;
|
4740
|
-
|
4741
|
-
up.boot = up.bus.boot;
|
4742
|
-
|
4743
4746
|
}).call(this);
|
4744
4747
|
|
4745
4748
|
/**
|
@@ -6628,7 +6631,7 @@ is built from these functions. You can use them to extend Unpoly from your
|
|
6628
6631
|
|
6629
6632
|
(function() {
|
6630
6633
|
up.dom = (function($) {
|
6631
|
-
var autofocus, bestMatchingSteps, bestPreflightSelector, config, destroy, detectScriptFixes, emitFragmentInserted, emitFragmentKept, extract, findKeepPlan, first, firstInLayer, firstInPriority, fixScripts, hello, isRealElement,
|
6634
|
+
var autofocus, bestMatchingSteps, bestPreflightSelector, config, destroy, detectScriptFixes, emitFragmentInserted, emitFragmentKept, extract, findKeepPlan, first, firstInLayer, firstInPriority, fixScripts, hello, isRealElement, layerOf, matchesLayer, parseResponseDoc, processResponse, reload, replace, reset, resolveSelector, setSource, shouldExtractTitle, shouldLogDestruction, shouldSwapElementsDirectly, source, swapElements, swapElementsDirectly, transferKeepableElements, u, updateHistoryAndTitle;
|
6632
6635
|
u = up.util;
|
6633
6636
|
|
6634
6637
|
/**
|
@@ -6857,7 +6860,7 @@ is built from these functions. You can use them to extend Unpoly from your
|
|
6857
6860
|
@stable
|
6858
6861
|
*/
|
6859
6862
|
replace = function(selectorOrElement, url, options) {
|
6860
|
-
var e, failureOptions, fullLoad, improvedFailTarget, improvedTarget, onFailure, onSuccess, promise, request, successOptions;
|
6863
|
+
var e, error, failureOptions, fullLoad, improvedFailTarget, improvedTarget, onFailure, onSuccess, promise, request, successOptions;
|
6861
6864
|
options = u.options(options);
|
6862
6865
|
options.inspectResponse = fullLoad = function() {
|
6863
6866
|
return up.browser.navigate(url, u.only(options, 'method', 'data'));
|
@@ -7153,8 +7156,8 @@ is built from these functions. You can use them to extend Unpoly from your
|
|
7153
7156
|
options.keepPlans = transferKeepableElements($old, $new, options);
|
7154
7157
|
clean = up.syntax.prepareClean($old);
|
7155
7158
|
replacement = function() {
|
7156
|
-
if (
|
7157
|
-
|
7159
|
+
if (shouldSwapElementsDirectly($old, $new, transition, options)) {
|
7160
|
+
swapElementsDirectly($old, $new);
|
7158
7161
|
transition = false;
|
7159
7162
|
} else {
|
7160
7163
|
$new.insertBefore($old);
|
@@ -7174,10 +7177,12 @@ is built from these functions. You can use them to extend Unpoly from your
|
|
7174
7177
|
}
|
7175
7178
|
return promise;
|
7176
7179
|
};
|
7177
|
-
|
7178
|
-
|
7180
|
+
shouldSwapElementsDirectly = function($old, $new, transition, options) {
|
7181
|
+
var $both;
|
7182
|
+
$both = $old.add($new);
|
7183
|
+
return $old.is('body') || !up.motion.willAnimate($both, transition, options);
|
7179
7184
|
};
|
7180
|
-
|
7185
|
+
swapElementsDirectly = function($old, $new) {
|
7181
7186
|
return $old.replaceWith($new);
|
7182
7187
|
};
|
7183
7188
|
transferKeepableElements = function($old, $new, options) {
|
@@ -7843,6 +7848,7 @@ You can define custom animations using [`up.transition()`](/up.transition) and
|
|
7843
7848
|
});
|
7844
7849
|
};
|
7845
7850
|
willAnimate = function($elements, animationOrTransition, options) {
|
7851
|
+
options = animateOptions(options);
|
7846
7852
|
return isEnabled() && !isNone(animationOrTransition) && options.duration > 0 && u.all($elements, u.isBodyDescendant);
|
7847
7853
|
};
|
7848
7854
|
skipAnimate = function($element, animation) {
|
@@ -8306,13 +8312,14 @@ You can define custom animations using [`up.transition()`](/up.transition) and
|
|
8306
8312
|
};
|
8307
8313
|
registerAnimation('move-to-top', function($ghost, options) {
|
8308
8314
|
var box, travelDistance;
|
8315
|
+
$ghost.css(translateCss(0, 0));
|
8309
8316
|
box = u.measure($ghost);
|
8310
8317
|
travelDistance = box.top + box.height;
|
8311
|
-
$ghost.css(translateCss(0, 0));
|
8312
8318
|
return animate($ghost, translateCss(0, -travelDistance), options);
|
8313
8319
|
});
|
8314
8320
|
registerAnimation('move-from-top', function($ghost, options) {
|
8315
8321
|
var box, travelDistance;
|
8322
|
+
$ghost.css(translateCss(0, 0));
|
8316
8323
|
box = u.measure($ghost);
|
8317
8324
|
travelDistance = box.top + box.height;
|
8318
8325
|
$ghost.css(translateCss(0, -travelDistance));
|
@@ -8320,13 +8327,14 @@ You can define custom animations using [`up.transition()`](/up.transition) and
|
|
8320
8327
|
});
|
8321
8328
|
registerAnimation('move-to-bottom', function($ghost, options) {
|
8322
8329
|
var box, travelDistance;
|
8330
|
+
$ghost.css(translateCss(0, 0));
|
8323
8331
|
box = u.measure($ghost);
|
8324
8332
|
travelDistance = u.clientSize().height - box.top;
|
8325
|
-
$ghost.css(translateCss(0, 0));
|
8326
8333
|
return animate($ghost, translateCss(0, travelDistance), options);
|
8327
8334
|
});
|
8328
8335
|
registerAnimation('move-from-bottom', function($ghost, options) {
|
8329
8336
|
var box, travelDistance;
|
8337
|
+
$ghost.css(translateCss(0, 0));
|
8330
8338
|
box = u.measure($ghost);
|
8331
8339
|
travelDistance = u.clientSize().height - box.top;
|
8332
8340
|
$ghost.css(translateCss(0, travelDistance));
|
@@ -8334,13 +8342,14 @@ You can define custom animations using [`up.transition()`](/up.transition) and
|
|
8334
8342
|
});
|
8335
8343
|
registerAnimation('move-to-left', function($ghost, options) {
|
8336
8344
|
var box, travelDistance;
|
8345
|
+
$ghost.css(translateCss(0, 0));
|
8337
8346
|
box = u.measure($ghost);
|
8338
8347
|
travelDistance = box.left + box.width;
|
8339
|
-
$ghost.css(translateCss(0, 0));
|
8340
8348
|
return animate($ghost, translateCss(-travelDistance, 0), options);
|
8341
8349
|
});
|
8342
8350
|
registerAnimation('move-from-left', function($ghost, options) {
|
8343
8351
|
var box, travelDistance;
|
8352
|
+
$ghost.css(translateCss(0, 0));
|
8344
8353
|
box = u.measure($ghost);
|
8345
8354
|
travelDistance = box.left + box.width;
|
8346
8355
|
$ghost.css(translateCss(-travelDistance, 0));
|
@@ -8348,13 +8357,14 @@ You can define custom animations using [`up.transition()`](/up.transition) and
|
|
8348
8357
|
});
|
8349
8358
|
registerAnimation('move-to-right', function($ghost, options) {
|
8350
8359
|
var box, travelDistance;
|
8360
|
+
$ghost.css(translateCss(0, 0));
|
8351
8361
|
box = u.measure($ghost);
|
8352
8362
|
travelDistance = u.clientSize().width - box.left;
|
8353
|
-
$ghost.css(translateCss(0, 0));
|
8354
8363
|
return animate($ghost, translateCss(travelDistance, 0), options);
|
8355
8364
|
});
|
8356
8365
|
registerAnimation('move-from-right', function($ghost, options) {
|
8357
8366
|
var box, travelDistance;
|
8367
|
+
$ghost.css(translateCss(0, 0));
|
8358
8368
|
box = u.measure($ghost);
|
8359
8369
|
travelDistance = u.clientSize().width - box.left;
|
8360
8370
|
$ghost.css(translateCss(travelDistance, 0));
|
@@ -8384,6 +8394,7 @@ You can define custom animations using [`up.transition()`](/up.transition) and
|
|
8384
8394
|
morph: morph,
|
8385
8395
|
animate: animate,
|
8386
8396
|
animateOptions: animateOptions,
|
8397
|
+
willAnimate: willAnimate,
|
8387
8398
|
finish: finish,
|
8388
8399
|
transition: registerTransition,
|
8389
8400
|
animation: registerAnimation,
|