unpoly-rails 0.36.2 → 0.37.0

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.

Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +1 -1
  4. data/dist/unpoly.js +137 -73
  5. data/dist/unpoly.min.js +3 -3
  6. data/lib/assets/javascripts/unpoly/bus.coffee +2 -2
  7. data/lib/assets/javascripts/unpoly/dom/extract_plan.coffee +4 -2
  8. data/lib/assets/javascripts/unpoly/dom.coffee +26 -12
  9. data/lib/assets/javascripts/unpoly/form.coffee +19 -1
  10. data/lib/assets/javascripts/unpoly/layout.coffee +1 -1
  11. data/lib/assets/javascripts/unpoly/link.coffee +9 -2
  12. data/lib/assets/javascripts/unpoly/modal.coffee +1 -0
  13. data/lib/assets/javascripts/unpoly/popup.coffee +1 -0
  14. data/lib/assets/javascripts/unpoly/syntax.coffee +48 -29
  15. data/lib/assets/javascripts/unpoly/util.coffee +12 -5
  16. data/lib/unpoly/rails/version.rb +1 -1
  17. data/package.json +1 -1
  18. data/spec_app/Gemfile.lock +1 -1
  19. data/spec_app/app/assets/javascripts/integration_test.coffee +4 -0
  20. data/spec_app/app/controllers/replace_test_controller.rb +5 -0
  21. data/spec_app/app/views/pages/start.erb +4 -0
  22. data/spec_app/app/views/replace_test/_nav.erb +6 -0
  23. data/spec_app/app/views/replace_test/page1.erb +14 -0
  24. data/spec_app/app/views/replace_test/page2.erb +14 -0
  25. data/spec_app/config/routes.rb +1 -0
  26. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +1 -2
  27. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +69 -1
  28. data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +5 -3
  29. data/spec_app/spec/javascripts/up/history_spec.js.coffee +174 -153
  30. data/spec_app/spec/javascripts/up/link_spec.js.coffee +77 -19
  31. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -1
  32. data/spec_app/spec/javascripts/up/util_spec.js.coffee +34 -0
  33. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2aa0a330c2aabe87658a477bfa125f86a0dca53e
4
- data.tar.gz: 548a655f32784b743d0d772ec32acc23aff4f04b
3
+ metadata.gz: 12f680d0e152470791793c144a1cbd2dcd6bd336
4
+ data.tar.gz: 21808898aa425e35c06c63fbdad3abbce3b7b3c8
5
5
  SHA512:
6
- metadata.gz: 988f23b929ccd1de90bfe44f12165cbfc9034bd26070c19d8bddeb2895445b8b4c9169a6bc54f48339dd836f24f878600cdf4ab0a0b181a9e4d819796abd3d79
7
- data.tar.gz: 825c4fead2631c7ee80811ae2f7e8ac0e82cbaa31ef9333abfa5aa2a2f0e550e9bb91a16708a52b8a50fd5a7a187e454b4fb06fd94564428e3118705a8b017df
6
+ metadata.gz: eff3e86b52040276213bad2591b68a7699c755ccac7093e70c33ae743608507ce4417a24c12e4f2975b507a1a6ceda546a2d6ae31effcf99cbc883f5da2c67ae
7
+ data.tar.gz: 05780d9786d8cf18c979b4e3b51fde5779631a7c91d2a7afa0f190651c790c72db660c31a9b2f52add0834728cc08982eca77051aa96f0dea059be5f9c1f51d7
data/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ 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.37.0
10
+ ------
11
+
12
+ ### Compatible changes
13
+
14
+ - Fix a bug where [replacing](/up.replace) the `<body>` element would not trigger [destructor functions](/up.compiler#cleaning-up-after-yourself) in the old `<body>`.
15
+ - Fix a bug where [`[up-layer]`](/up-layer) attributes or `{ layer }` options were ignored.
16
+ - [`a[up-target]`](/a-up-target) and [`form[up-target]`] get a new modifying attribute `[up-fail-layer]`.
17
+ Use it to set the layer to update if the server sends a non-200 status code. Valid values are `auto`, `page`, `modal` and `popup`.
18
+ - JavaScript functions like [`up.replace()`](/up.replace) or [`up.submit()`](/up.submit) now have a `{ failLayer }` option.
19
+
20
+
9
21
  0.36.2
10
22
  ------
11
23
 
data/README.md CHANGED
@@ -53,7 +53,7 @@ To run RSpec tests for the `unpoly-rails` gem:
53
53
  We are currently feeding four release channels:
54
54
 
55
55
  - Manual download from Github
56
- npm
56
+ - npm
57
57
  - Bower (which is based on Git and version tags)
58
58
  - Rubygems (as the `unpoly-rails` gem)
59
59
 
data/dist/unpoly.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  (function() {
7
7
  window.up = {
8
- version: "0.36.2",
8
+ version: "0.37.0",
9
9
  renamedModule: function(oldName, newName) {
10
10
  return typeof Object.defineProperty === "function" ? Object.defineProperty(up, oldName, {
11
11
  get: function() {
@@ -41,7 +41,7 @@ that might save you from loading something like [Underscore.js](http://underscor
41
41
  @function up.util.noop
42
42
  @experimental
43
43
  */
44
- var $createElementFromSelector, $createPlaceholder, $submittingButton, ANIMATION_DEFERRED_KEY, DivertibleChain, ESCAPE_HTML_ENTITY_MAP, all, any, appendRequestData, assign, assignPolyfill, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detachWith, detect, documentHasVerticalScrollbar, each, escapeHtml, escapePressed, evalOption, except, extractOptions, fail, findWithSelf, finishCssAnimate, fixedToAbsolute, flatten, forceCompositing, forceRepaint, horizontalScreenHalf, identity, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFixed, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isResolvedPromise, isStandardPort, isString, isTruthy, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, map, margins, measure, memoize, merge, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, opacity, openConfig, openTagPattern, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, previewable, promiseTimer, reject, rejectedPromise, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, sequence, setMissingAttrs, setTimer, submittedValue, temporaryCss, times, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, whenReady;
44
+ var $createElementFromSelector, $createPlaceholder, $submittingButton, ANIMATION_DEFERRED_KEY, DivertibleChain, ESCAPE_HTML_ENTITY_MAP, all, any, appendRequestData, assign, assignPolyfill, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detachWith, detect, documentHasVerticalScrollbar, each, escapeHtml, escapePressed, evalOption, except, extractOptions, fail, findWithSelf, finishCssAnimate, fixedToAbsolute, flatten, forceCompositing, forceRepaint, horizontalScreenHalf, identity, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFixed, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isResolvedPromise, isStandardPort, isString, isTruthy, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, map, margins, measure, memoize, merge, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, opacity, openConfig, openTagPattern, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, previewable, promiseTimer, reject, rejectedPromise, remove, renameKey, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, sequence, setMissingAttrs, setTimer, submittedValue, temporaryCss, times, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, whenReady;
45
45
  noop = $.noop;
46
46
 
47
47
  /**
@@ -55,17 +55,17 @@ that might save you from loading something like [Underscore.js](http://underscor
55
55
  @internal
56
56
  */
57
57
  memoize = function(func) {
58
- var cache, cached;
59
- cache = void 0;
58
+ var cached, cachedValue;
59
+ cachedValue = void 0;
60
60
  cached = false;
61
61
  return function() {
62
62
  var args;
63
63
  args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
64
64
  if (cached) {
65
- return cache;
65
+ return cachedValue;
66
66
  } else {
67
67
  cached = true;
68
- return cache = func.apply(null, args);
68
+ return cachedValue = func.apply(null, args);
69
69
  }
70
70
  };
71
71
  };
@@ -2101,6 +2101,9 @@ that might save you from loading something like [Underscore.js](http://underscor
2101
2101
  delete object[key];
2102
2102
  return value;
2103
2103
  };
2104
+ renameKey = function(object, oldKey, newKey) {
2105
+ return object[newKey] = pluckKey(object, oldKey);
2106
+ };
2104
2107
  pluckData = function(elementOrSelector, key) {
2105
2108
  var $element, value;
2106
2109
  $element = $(elementOrSelector);
@@ -2269,6 +2272,10 @@ that might save you from loading something like [Underscore.js](http://underscor
2269
2272
 
2270
2273
  /**
2271
2274
  @function up.util.sequence
2275
+ @param {Array<Function>} functions...
2276
+ @return {Function}
2277
+ A function that will call all `functions` if called.
2278
+
2272
2279
  @internal
2273
2280
  */
2274
2281
  sequence = function() {
@@ -2320,7 +2327,7 @@ that might save you from loading something like [Underscore.js](http://underscor
2320
2327
  /**
2321
2328
  Like `$old.replaceWith($new)`, but keeps event handlers bound to `$old`.
2322
2329
 
2323
- Note that this is a memory leak unless you re-attach `$new` to the DOM aferwards.
2330
+ Note that this is a memory leak unless you re-attach `$old` to the DOM aferwards.
2324
2331
 
2325
2332
  @function up.util.detachWith
2326
2333
  @internal
@@ -2361,7 +2368,6 @@ that might save you from loading something like [Underscore.js](http://underscor
2361
2368
  return !!object;
2362
2369
  };
2363
2370
  return {
2364
- isDetached: isDetached,
2365
2371
  requestDataAsArray: requestDataAsArray,
2366
2372
  requestDataAsQuery: requestDataAsQuery,
2367
2373
  appendRequestData: appendRequestData,
@@ -2458,6 +2464,7 @@ that might save you from loading something like [Underscore.js](http://underscor
2458
2464
  error: fail,
2459
2465
  pluckData: pluckData,
2460
2466
  pluckKey: pluckKey,
2467
+ renameKey: renameKey,
2461
2468
  extractOptions: extractOptions,
2462
2469
  isDetached: isDetached,
2463
2470
  noop: noop,
@@ -3235,8 +3242,8 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
3235
3242
  In case you want to attach structured data to the event you're observing,
3236
3243
  you can serialize the data to JSON and put it into an `[up-data]` attribute:
3237
3244
 
3238
- <span class="person" up-data="{ age: 18, name: 'Bob' }">Bob</span>
3239
- <span class="person" up-data="{ age: 22, name: 'Jim' }">Jim</span>
3245
+ <span class='person' up-data='{ "age": 18, "name": "Bob" }'>Bob</span>
3246
+ <span class='person' up-data='{ "age": 22, "name": "Jim" }'>Jim</span>
3240
3247
 
3241
3248
  The JSON will parsed and handed to your event handler as a third argument:
3242
3249
 
@@ -3952,7 +3959,7 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
3952
3959
  var slice = [].slice;
3953
3960
 
3954
3961
  up.syntax = (function($) {
3955
- var DESTRUCTIBLE_CLASS, DESTRUCTORS_KEY, addDestructor, applyCompiler, buildCompiler, clean, compile, compiler, compilers, data, discoverDestructors, insertCompiler, macro, macros, reset, snapshot, u;
3962
+ var DESTRUCTIBLE_CLASS, DESTRUCTORS_KEY, addDestructor, applyCompiler, buildCompiler, clean, compile, compiler, compilers, data, insertCompiler, macro, macros, normalizeDestructor, prepareClean, removeDestructors, reset, snapshot, u;
3956
3963
  u = up.util;
3957
3964
  DESTRUCTIBLE_CLASS = 'up-destructible';
3958
3965
  DESTRUCTORS_KEY = 'up-destructors';
@@ -4059,10 +4066,10 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4059
4066
  For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
4060
4067
  might attach the location and names of its marker pins:
4061
4068
 
4062
- <div class="google-map" up-data="[
4063
- { lat: 48.36, lng: 10.99, title: 'Friedberg' },
4064
- { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
4065
- ]"></div>
4069
+ <div class='google-map' up-data='[
4070
+ { "lat": 48.36, "lng": 10.99, "title": "Friedberg" },
4071
+ { "lat": 48.75, "lng": 11.45, "title": "Ingolstadt" }
4072
+ ]'></div>
4066
4073
 
4067
4074
  The JSON will parsed and handed to your compiler as a second argument:
4068
4075
 
@@ -4223,36 +4230,44 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4223
4230
  return queue.splice(index, 0, newCompiler);
4224
4231
  };
4225
4232
  applyCompiler = function(compiler, $jqueryElement, nativeElement) {
4226
- var destructor, i, len, ref, results, returnValue, value;
4233
+ var returnValue, value;
4227
4234
  up.puts((!compiler.isDefault ? "Compiling '%s' on %o" : void 0), compiler.selector, nativeElement);
4228
4235
  if (compiler.keep) {
4229
4236
  value = u.isString(compiler.keep) ? compiler.keep : '';
4230
4237
  $jqueryElement.attr('up-keep', value);
4231
4238
  }
4232
4239
  returnValue = compiler.callback.apply(nativeElement, [$jqueryElement, data($jqueryElement)]);
4233
- ref = discoverDestructors(returnValue);
4234
- results = [];
4235
- for (i = 0, len = ref.length; i < len; i++) {
4236
- destructor = ref[i];
4237
- results.push(addDestructor($jqueryElement, destructor));
4238
- }
4239
- return results;
4240
+ return addDestructor($jqueryElement, returnValue);
4240
4241
  };
4241
- discoverDestructors = function(returnValue) {
4242
+
4243
+ /**
4244
+ Tries to find a list of destructors in a compiler's return value.
4245
+
4246
+ @param {Object} returnValue
4247
+ @return {Function|undefined}
4248
+ @internal
4249
+ */
4250
+ normalizeDestructor = function(returnValue) {
4242
4251
  if (u.isFunction(returnValue)) {
4243
- return [returnValue];
4244
- } else if (u.isArray(returnValue) && u.all(returnValue, u.isFunction)) {
4245
4252
  return returnValue;
4246
- } else {
4247
- return [];
4253
+ } else if (u.isArray(returnValue) && u.all(returnValue, u.isFunction)) {
4254
+ return u.sequence.apply(u, returnValue);
4248
4255
  }
4249
4256
  };
4250
- addDestructor = function($jqueryElement, destructor) {
4251
- var destructors;
4252
- $jqueryElement.addClass(DESTRUCTIBLE_CLASS);
4253
- destructors = $jqueryElement.data(DESTRUCTORS_KEY) || [];
4254
- destructors.push(destructor);
4255
- return $jqueryElement.data(DESTRUCTORS_KEY, destructors);
4257
+ addDestructor = function($element, newDestructor) {
4258
+ var elementDestructor;
4259
+ if (newDestructor = normalizeDestructor(newDestructor)) {
4260
+ $element.addClass(DESTRUCTIBLE_CLASS);
4261
+ elementDestructor = $element.data(DESTRUCTORS_KEY) || function() {
4262
+ return removeDestructors($element);
4263
+ };
4264
+ elementDestructor = u.sequence(elementDestructor, newDestructor);
4265
+ return $element.data(DESTRUCTORS_KEY, elementDestructor);
4266
+ }
4267
+ };
4268
+ removeDestructors = function($element) {
4269
+ $element.removeData(DESTRUCTORS_KEY);
4270
+ return $element.removeClass(DESTRUCTIBLE_CLASS);
4256
4271
  };
4257
4272
 
4258
4273
  /**
@@ -4283,8 +4298,8 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4283
4298
  $matches = $matches.filter(function() {
4284
4299
  var $match;
4285
4300
  $match = $(this);
4286
- return u.all($skipSubtrees, function(element) {
4287
- return $match.closest(element).length === 0;
4301
+ return u.all($skipSubtrees, function(skipSubtree) {
4302
+ return $match.closest(skipSubtree).length === 0;
4288
4303
  });
4289
4304
  });
4290
4305
  if ($matches.length) {
@@ -4317,19 +4332,25 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4317
4332
  @internal
4318
4333
  */
4319
4334
  clean = function($fragment) {
4320
- return u.findWithSelf($fragment, "." + DESTRUCTIBLE_CLASS).each(function() {
4321
- var $element, destructor, destructors, i, len;
4322
- $element = $(this);
4323
- destructors = $element.data(DESTRUCTORS_KEY);
4324
- if (destructors) {
4325
- for (i = 0, len = destructors.length; i < len; i++) {
4326
- destructor = destructors[i];
4327
- destructor();
4328
- }
4329
- $element.removeData(DESTRUCTORS_KEY);
4330
- return $element.removeClass(DESTRUCTIBLE_CLASS);
4335
+ return prepareClean($fragment)();
4336
+ };
4337
+
4338
+ /**
4339
+ @function up.syntax.prepareClean
4340
+ @param {jQuery} $fragment
4341
+ @return {Function}
4342
+ @internal
4343
+ */
4344
+ prepareClean = function($fragment) {
4345
+ var destructors;
4346
+ destructors = [];
4347
+ u.findWithSelf($fragment, "." + DESTRUCTIBLE_CLASS).each(function() {
4348
+ var destructor;
4349
+ if (destructor = $(this).data(DESTRUCTORS_KEY)) {
4350
+ return destructors.push(destructor);
4331
4351
  }
4332
4352
  });
4353
+ return u.sequence.apply(u, destructors);
4333
4354
  };
4334
4355
 
4335
4356
  /**
@@ -4342,7 +4363,7 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4342
4363
 
4343
4364
  You have an element with JSON data serialized into an `up-data` attribute:
4344
4365
 
4345
- <span class="person" up-data="{ age: 18, name: 'Bob' }">Bob</span>
4366
+ <span class='person' up-data='{ "age": 18, "name": "Bob" }'>Bob</span>
4346
4367
 
4347
4368
  Calling `up.syntax.data()` will deserialize the JSON string into a JavaScript object:
4348
4369
 
@@ -4365,10 +4386,10 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4365
4386
  For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
4366
4387
  might attach the location and names of its marker pins:
4367
4388
 
4368
- <div class="google-map" up-data="[
4369
- { lat: 48.36, lng: 10.99, title: 'Friedberg' },
4370
- { lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
4371
- ]"></div>
4389
+ <div class='google-map' up-data='[
4390
+ { "lat": 48.36, "lng": 10.99, "title": "Friedberg" },
4391
+ { "lat": 48.75, "lng": 11.45, "title": "Ingolstadt" }
4392
+ ]'></div>
4372
4393
 
4373
4394
  The JSON will parsed and handed to your compiler as a second argument:
4374
4395
 
@@ -4447,6 +4468,7 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4447
4468
  macro: macro,
4448
4469
  compile: compile,
4449
4470
  clean: clean,
4471
+ prepareClean: prepareClean,
4450
4472
  data: data
4451
4473
  };
4452
4474
  })(jQuery);
@@ -5254,7 +5276,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
5254
5276
  } else {
5255
5277
  $viewports = viewports();
5256
5278
  }
5257
- scrollTopsForUrl = lastScrollTops.get(url);
5279
+ scrollTopsForUrl = lastScrollTops.get(url) || {};
5258
5280
  return up.log.group('Restoring scroll positions for URL %s to %o', url, scrollTopsForUrl, function() {
5259
5281
  $viewports.each(function() {
5260
5282
  var $viewport, key, scrollTop;
@@ -5480,7 +5502,7 @@ is built from these functions. You can use them to extend Unpoly from your
5480
5502
 
5481
5503
  (function() {
5482
5504
  up.dom = (function($) {
5483
- var autofocus, bestMatchingSteps, bestPreflightSelector, config, destroy, emitFragmentInserted, emitFragmentKept, extract, filterScripts, findKeepPlan, first, firstInLayer, firstInPriority, hello, isRealElement, layerOf, matchesLayer, parseResponse, processResponse, reload, replace, reset, resolveSelector, setSource, shouldExtractTitle, source, swapBody, swapElements, transferKeepableElements, u, updateHistoryAndTitle;
5505
+ var autofocus, bestMatchingSteps, bestPreflightSelector, config, destroy, emitFragmentInserted, emitFragmentKept, extract, filterScripts, findKeepPlan, first, firstInLayer, firstInPriority, hello, isRealElement, isSingletonElement, layerOf, matchesLayer, parseResponse, processResponse, reload, replace, reset, resolveSelector, setSource, shouldExtractTitle, source, swapElements, swapSingletonElement, transferKeepableElements, u, updateHistoryAndTitle;
5484
5506
  u = up.util;
5485
5507
 
5486
5508
  /**
@@ -5701,6 +5723,8 @@ is built from these functions. You can use them to extend Unpoly from your
5701
5723
  same layer as the element that triggered the replacement (see `options.origin`).
5702
5724
  If that element is not known, or no match was found in that layer,
5703
5725
  Unpoly will search in other layers, starting from the topmost layer.
5726
+ @param {String} [options.failLayer='auto']
5727
+ The name of the layer that ought to be updated if the server sends a non-200 status code.
5704
5728
 
5705
5729
  @return {Promise}
5706
5730
  A promise that will be resolved when the page has been updated.
@@ -5725,6 +5749,8 @@ is built from these functions. You can use them to extend Unpoly from your
5725
5749
  humanizedTarget: 'failure target',
5726
5750
  provideTarget: void 0
5727
5751
  });
5752
+ u.renameKey(failureOptions, 'failTransition', 'transition');
5753
+ u.renameKey(failureOptions, 'failLayer', 'layer');
5728
5754
  target = bestPreflightSelector(selectorOrElement, successOptions);
5729
5755
  failTarget = bestPreflightSelector(options.failTarget, failureOptions);
5730
5756
  request = {
@@ -5797,8 +5823,6 @@ is built from these functions. You can use them to extend Unpoly from your
5797
5823
  }
5798
5824
  }
5799
5825
  } else {
5800
- options.transition = options.failTransition;
5801
- options.failTransition = void 0;
5802
5826
  if (isReloadable) {
5803
5827
  if (options.history !== false) {
5804
5828
  options.history = url;
@@ -5957,7 +5981,7 @@ is built from these functions. You can use them to extend Unpoly from your
5957
5981
  }
5958
5982
  };
5959
5983
  swapElements = function($old, $new, pseudoClass, transition, options) {
5960
- var $wrapper, keepPlan, promise, replacement;
5984
+ var $wrapper, clean, keepPlan, promise, replacement;
5961
5985
  transition || (transition = 'none');
5962
5986
  if (options.source === 'keep') {
5963
5987
  options = u.merge(options, {
@@ -5984,10 +6008,11 @@ is built from these functions. You can use them to extend Unpoly from your
5984
6008
  emitFragmentKept(keepPlan);
5985
6009
  promise = u.resolvedPromise();
5986
6010
  } else {
6011
+ options.keepPlans = transferKeepableElements($old, $new, options);
6012
+ clean = up.syntax.prepareClean($old);
5987
6013
  replacement = function() {
5988
- options.keepPlans = transferKeepableElements($old, $new, options);
5989
- if ($old.is('body')) {
5990
- swapBody($old, $new);
6014
+ if (isSingletonElement($old)) {
6015
+ swapSingletonElement($old, $new);
5991
6016
  transition = false;
5992
6017
  } else {
5993
6018
  $new.insertBefore($old);
@@ -6000,13 +6025,17 @@ is built from these functions. You can use them to extend Unpoly from your
6000
6025
  return up.morph($old, $new, transition, options);
6001
6026
  };
6002
6027
  promise = destroy($old, {
6028
+ clean: clean,
6003
6029
  animation: replacement
6004
6030
  });
6005
6031
  }
6006
6032
  return promise;
6007
6033
  };
6008
- swapBody = function($oldBody, $newBody) {
6009
- return $oldBody.replaceWith($newBody);
6034
+ isSingletonElement = function($element) {
6035
+ return $element.is('body');
6036
+ };
6037
+ swapSingletonElement = function($old, $new) {
6038
+ return $old.replaceWith($new);
6010
6039
  };
6011
6040
  transferKeepableElements = function($old, $new, options) {
6012
6041
  var $keepable, $keepableClone, i, keepPlans, keepable, len, plan, ref;
@@ -6351,6 +6380,9 @@ is built from these functions. You can use them to extend Unpoly from your
6351
6380
  */
6352
6381
  destroy = function(selectorOrElement, options) {
6353
6382
  var $element, animateOptions, animationDeferred, destroyMessage, destroyedMessage;
6383
+ options = u.options(options, {
6384
+ animation: false
6385
+ });
6354
6386
  $element = $(selectorOrElement);
6355
6387
  if (!$element.is('.up-placeholder, .up-tooltip, .up-modal, .up-popup')) {
6356
6388
  destroyMessage = ['Destroying fragment %o', $element.get(0)];
@@ -6362,15 +6394,15 @@ is built from these functions. You can use them to extend Unpoly from your
6362
6394
  $element: $element,
6363
6395
  message: destroyMessage
6364
6396
  })) {
6365
- options = u.options(options, {
6366
- animation: false
6367
- });
6368
6397
  animateOptions = up.motion.animateOptions(options);
6369
6398
  $element.addClass('up-destroying');
6370
6399
  updateHistoryAndTitle(options);
6371
6400
  animationDeferred = u.presence(options.animation, u.isDeferred) || up.motion.animate($element, options.animation, animateOptions);
6372
6401
  animationDeferred.then(function() {
6373
- up.syntax.clean($element);
6402
+ options.clean || (options.clean = function() {
6403
+ return up.syntax.clean($element);
6404
+ });
6405
+ options.clean();
6374
6406
  up.emit('up:fragment:destroyed', {
6375
6407
  $element: $element,
6376
6408
  message: destroyedMessage
@@ -6379,7 +6411,7 @@ is built from these functions. You can use them to extend Unpoly from your
6379
6411
  });
6380
6412
  return animationDeferred;
6381
6413
  } else {
6382
- return $.Deferred();
6414
+ return u.unresolvableDeferred();
6383
6415
  }
6384
6416
  };
6385
6417
 
@@ -6612,13 +6644,18 @@ is built from these functions. You can use them to extend Unpoly from your
6612
6644
  this.selector = up.dom.resolveSelector(selector, options.origin);
6613
6645
  this.transition = options.transition || options.animation || 'none';
6614
6646
  this.response = options.response;
6647
+ this.oldLayer = options.layer;
6615
6648
  this.steps = this.parseSteps();
6616
6649
  }
6617
6650
 
6618
6651
  ExtractPlan.prototype.findOld = function() {
6619
- return u.each(this.steps, function(step) {
6620
- return step.$old = up.dom.first(step.selector, this.options);
6621
- });
6652
+ return u.each(this.steps, (function(_this) {
6653
+ return function(step) {
6654
+ return step.$old = up.dom.first(step.selector, {
6655
+ layer: _this.oldLayer
6656
+ });
6657
+ };
6658
+ })(this));
6622
6659
  };
6623
6660
 
6624
6661
  ExtractPlan.prototype.findNew = function() {
@@ -8190,6 +8227,9 @@ new page is loading.
8190
8227
  If set to `auto` (default), Unpoly will try to find a match in the
8191
8228
  same layer as the given link. If no match was found in that layer,
8192
8229
  Unpoly will search in other layers, starting from the topmost layer.
8230
+ @param {String} [options.failLayer='auto']
8231
+ The name of the layer that ought to be updated if the server sends a non-200 status code.
8232
+
8193
8233
  @return {Promise}
8194
8234
  A promise that will be resolved when the link destination
8195
8235
  has been loaded and rendered.
@@ -8212,6 +8252,7 @@ new page is loading.
8212
8252
  options.method = followMethod($link, options);
8213
8253
  options.origin = u.option(options.origin, $link);
8214
8254
  options.layer = u.option(options.layer, $link.attr('up-layer'), 'auto');
8255
+ options.failLayer = u.option(options.failLayer, $link.attr('up-fail-layer'), 'auto');
8215
8256
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
8216
8257
  options = u.merge(options, up.motion.animateOptions(options, $link));
8217
8258
  return up.browser.whenConfirmed(options).then(function() {
@@ -8406,9 +8447,12 @@ new page is loading.
8406
8447
  The name of the layer that ought to be updated. Valid values are
8407
8448
  `auto`, `page`, `modal` and `popup`.
8408
8449
 
8409
- If set to `auto` (default), Unpoly will try to find a match in the
8410
- same layer as the given link. If no match was found in that layer,
8450
+ If set to `auto` (default), Unpoly will try to find a match in the link's layer.
8451
+ If no match was found in that layer,
8411
8452
  Unpoly will search in other layers, starting from the topmost layer.
8453
+ @param {String} [up-fail-layer='auto']
8454
+ The name of the layer that ought to be updated if the server sends a
8455
+ non-200 status code.
8412
8456
  @param [up-history]
8413
8457
  Whether to push an entry to the browser history when following the link.
8414
8458
 
@@ -8738,6 +8782,13 @@ open dialogs with sub-forms, etc. all without losing form state.
8738
8782
  @param {Object} [options.headers={}]
8739
8783
  An object of additional header key/value pairs to send along
8740
8784
  with the request.
8785
+ @param {String} [options.layer='auto']
8786
+ The name of the layer that ought to be updated. Valid values are
8787
+ `auto`, `page`, `modal` and `popup`.
8788
+
8789
+ If set to `auto` (default), Unpoly will try to find a match in the form's layer.
8790
+ @param {String} [options.failLayer='auto']
8791
+ The name of the layer that ought to be updated if the server sends a non-200 status code.
8741
8792
  @return {Promise}
8742
8793
  A promise for the successful form submission.
8743
8794
  @stable
@@ -8760,6 +8811,7 @@ open dialogs with sub-forms, etc. all without losing form state.
8760
8811
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'));
8761
8812
  options.origin = u.option(options.origin, $form);
8762
8813
  options.layer = u.option(options.layer, $form.attr('up-layer'), 'auto');
8814
+ options.failLayer = u.option(options.failLayer, $form.attr('up-fail-layer'), 'auto');
8763
8815
  options.data = u.requestDataFromForm($form);
8764
8816
  options = u.merge(options, up.motion.animateOptions(options, $form));
8765
8817
  if (options.validate) {
@@ -9100,7 +9152,7 @@ open dialogs with sub-forms, etc. all without losing form state.
9100
9152
  if (switcher) {
9101
9153
  return $(switcher);
9102
9154
  } else {
9103
- return u.fail('Could not find [up-switch] field for %o', $element.get(0));
9155
+ return u.fail('Could not find [up-switch] field for %o', $target.get(0));
9104
9156
  }
9105
9157
  };
9106
9158
 
@@ -9196,6 +9248,16 @@ open dialogs with sub-forms, etc. all without losing form state.
9196
9248
  Alternately you can use an attribute `data-method`
9197
9249
  ([Rails UJS](https://github.com/rails/jquery-ujs/wiki/Unobtrusive-scripting-support-for-jQuery))
9198
9250
  or `method` (vanilla HTML) for the same purpose.
9251
+ @param {String} [up-layer='auto']
9252
+ The name of the layer that ought to be updated. Valid values are
9253
+ `auto`, `page`, `modal` and `popup`.
9254
+
9255
+ If set to `auto` (default), Unpoly will try to find a match in the form's layer.
9256
+ If no match was found in that layer,
9257
+ Unpoly will search in other layers, starting from the topmost layer.
9258
+ @param {String} [up-fail-layer='auto']
9259
+ The name of the layer that ought to be updated if the server sends a
9260
+ non-200 status code.
9199
9261
  @param {String} [up-reveal='true']
9200
9262
  Whether to reveal the target element within its viewport before updating.
9201
9263
  @param {String} [up-restore-scroll='false']
@@ -9802,6 +9864,7 @@ The HTML of a popup element is simply this:
9802
9864
  options.confirm = u.option(options.confirm, $anchor.attr('up-confirm'));
9803
9865
  options.method = up.link.followMethod($anchor, options);
9804
9866
  options.layer = 'popup';
9867
+ options.failLayer = u.option(options.failLayer, $anchor.attr('up-fail-layer'), 'auto');
9805
9868
  animateOptions = up.motion.animateOptions(options, $anchor, {
9806
9869
  duration: config.openDuration,
9807
9870
  easing: config.openEasing
@@ -10548,6 +10611,7 @@ or function.
10548
10611
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
10549
10612
  options.method = up.link.followMethod($link, options);
10550
10613
  options.layer = 'modal';
10614
+ options.failLayer = u.option(options.failLayer, $link.attr('up-fail-layer'), 'auto');
10551
10615
  animateOptions = up.motion.animateOptions(options, $link, {
10552
10616
  duration: flavorDefault('openDuration', options.flavor),
10553
10617
  easing: flavorDefault('openEasing', options.flavor)