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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6df6e7fea86df29cccc4752d549cd7083be2341f
4
- data.tar.gz: a7375db94c1351ed6d5f46ef9ee856898fe1b5b7
2
+ SHA256:
3
+ metadata.gz: f2684c206748c31c831f0891eafffb27bfaab7f995bec6fe97add98989f2cdac
4
+ data.tar.gz: 37a9f61758d26c768b7b84f17b42df9029105555e3216e74b34b8749d71ca897
5
5
  SHA512:
6
- metadata.gz: 5ee1ee46c9d12ad6c40252fa31b8d877f5f32f85d3df461da82c02f7479d81f2a58d104bd3998aa478ace1215762d9be1c7e8d12e341be26aaeeb69ca399a239
7
- data.tar.gz: 4e835e9180d7b051bb9ec126b760fc7766711ae2bca4993cc0f9fe04785e8c53a039c3ae4aed850a44bf2165b1d4adbe64b19c2ee383b8c897fccd8607114602
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
- Unreleased
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.0",
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
- Server protocol
3519
+ Browser support
3519
3520
  ===============
3520
3521
 
3521
- You rarely need to change server-side code
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
- When Unpoly boots, it will look for this cookie and configure its behavior accordingly.
3684
- The cookie is then deleted in order to not affect following requests.
3524
+ Chrome, Firefox, Edge, Safari
3525
+ : Full support
3685
3526
 
3686
- The **simplest implementation** is to set this cookie for every request that is neither
3687
- `GET` nor contains an [`X-Up-Target` header](/#optimizing-responses). For all other requests
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.protocol
3533
+ @class up.browser
3692
3534
  */
3693
3535
 
3694
3536
  (function() {
3695
- up.protocol = (function($) {
3696
- var config, csrfParam, csrfToken, initialRequestMethod, locationFromXhr, methodFromXhr, reset, titleFromXhr, u;
3697
- u = up.util;
3537
+ var slice = [].slice;
3698
3538
 
3699
- /**
3700
- @function up.protocol.locationFromXhr
3701
- @internal
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
- @function up.protocol.titleFromXhr
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
- titleFromXhr = function(xhr) {
3712
- return xhr.getResponseHeader(config.titleHeader);
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
- @function up.protocol.methodFromXhr
3717
- @internal
3562
+ For mocking in specs.
3563
+
3564
+ @method submitForm
3718
3565
  */
3719
- methodFromXhr = function(xhr) {
3720
- var method;
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
- Server-side companion libraries like unpoly-rails set this cookie so we
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
- @function up.protocol.initialRequestMethod
3732
- @internal
3733
- */
3734
- initialRequestMethod = u.memoize(function() {
3735
- var methodFromServer;
3736
- methodFromServer = up.browser.popCookie(config.methodCookie);
3737
- return (methodFromServer || 'get').toLowerCase();
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
- onEscape = function(listener) {
4580
- return live('keydown', 'body', function(event) {
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
- Stops the given event from propagating and prevents the default action.
4589
-
4590
- @function up.bus.haltEvent
4629
+ @function up.protocol.titleFromXhr
4591
4630
  @internal
4592
4631
  */
4593
- haltEvent = function(event) {
4594
- event.stopImmediatePropagation();
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.bus.consumeAction
4637
+ @function up.protocol.methodFromXhr
4601
4638
  @internal
4602
4639
  */
4603
- consumeAction = function(event) {
4604
- haltEvent(event);
4605
- if (event.type !== 'up:action:consumed') {
4606
- return emit('up:action:consumed', {
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
- Makes a snapshot of the currently registered event listeners,
4615
- to later be restored through [`up.bus.reset()`](/up.bus.reset).
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
- snapshot = function() {
4620
- var description, number, results;
4621
- results = [];
4622
- for (number in liveUpDescriptions) {
4623
- description = liveUpDescriptions[number];
4624
- results.push(description.isDefault = true);
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
- Resets Unpoly to the state when it was booted.
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
- This is an internal method for to enable unit testing.
4651
- Don't use this in production.
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
- Emits event [`up:framework:reset`](/up:framework:reset).
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
- @function up.reset
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
- @event up:framework:reset
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
- **This is called automatically** by including the Unpoly JavaScript files.
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
- Unpoly will not boot if the current browser is [not supported](/up.browser.isSupported).
4681
- This leaves you with a classic server-side application on legacy browsers.
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
- @function up.boot
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
- @event up:framework:boot
4713
- @internal
4704
+ <meta name='csrf-token' content='secret12345'>
4705
+
4706
+ @experimental
4714
4707
  */
4715
- live('up:framework:booted', snapshot);
4716
- live('up:framework:reset', resetBus);
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
- knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
4719
- on: live,
4720
- off: unbind,
4721
- emit: emit,
4722
- nobodyPrevents: nobodyPrevents,
4723
- whenEmitted: whenEmitted,
4724
- onEscape: onEscape,
4725
- emitReset: emitReset,
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, isSingletonElement, layerOf, matchesLayer, parseResponseDoc, processResponse, reload, replace, reset, resolveSelector, setSource, shouldExtractTitle, shouldLogDestruction, source, swapElements, swapSingletonElement, transferKeepableElements, u, updateHistoryAndTitle;
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 (isSingletonElement($old)) {
7157
- swapSingletonElement($old, $new);
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
- isSingletonElement = function($element) {
7178
- return $element.is('body');
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
- swapSingletonElement = function($old, $new) {
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,