upjs-rails 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/dist/up.js +929 -374
  4. data/dist/up.min.js +2 -2
  5. data/lib/assets/javascripts/up/browser.js.coffee +31 -14
  6. data/lib/assets/javascripts/up/bus.js.coffee +87 -22
  7. data/lib/assets/javascripts/up/flow.js.coffee +119 -43
  8. data/lib/assets/javascripts/up/form.js.coffee +188 -57
  9. data/lib/assets/javascripts/up/link.js.coffee +57 -21
  10. data/lib/assets/javascripts/up/modal.js.coffee +77 -63
  11. data/lib/assets/javascripts/up/motion.js.coffee +10 -9
  12. data/lib/assets/javascripts/up/popup.js.coffee +54 -40
  13. data/lib/assets/javascripts/up/proxy.js.coffee +46 -17
  14. data/lib/assets/javascripts/up/rails.js.coffee +22 -4
  15. data/lib/assets/javascripts/up/syntax.js.coffee +2 -2
  16. data/lib/assets/javascripts/up/util.js.coffee +100 -16
  17. data/lib/upjs/rails/inspector.rb +3 -3
  18. data/lib/upjs/rails/version.rb +1 -1
  19. data/spec_app/Gemfile.lock +1 -4
  20. data/spec_app/app/controllers/test_controller.rb +2 -2
  21. data/spec_app/spec/controllers/test_controller_spec.rb +5 -5
  22. data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +9 -0
  23. data/spec_app/spec/javascripts/helpers/knife.js.coffee +0 -1
  24. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +4 -5
  25. data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +5 -0
  26. data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +8 -0
  27. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +26 -0
  28. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +203 -91
  29. data/spec_app/spec/javascripts/up/form_spec.js.coffee +244 -49
  30. data/spec_app/spec/javascripts/up/history_spec.js.coffee +8 -2
  31. data/spec_app/spec/javascripts/up/link_spec.js.coffee +83 -30
  32. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -17
  33. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +4 -4
  34. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +1 -1
  35. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +26 -16
  36. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +45 -13
  37. data/spec_app/spec/javascripts/up/rails_spec.js.coffee +48 -0
  38. data/spec_app/spec/javascripts/up/util_spec.js.coffee +48 -0
  39. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eec276bb6f7dc7dfdf81aebd61dbed1565b0c5f0
4
- data.tar.gz: 4afc1f416a3d147376fe884e7d2091d83ae2309b
3
+ metadata.gz: 0282df3df026ad3d48b9f10614eb73790b146243
4
+ data.tar.gz: 1fd5ddad76f7ab45668da13b90e7727a24d7f4ac
5
5
  SHA512:
6
- metadata.gz: f0166f436863417fa66826fb4978d72087454a0cd4d8cfa43070fdadc42a0da029ca4cc6115a9f4b4d3ea06fdee35de4d87e537ea71ef691a191df62dbffba42
7
- data.tar.gz: 141cf7507e68a2ab6409dda692c8cca53912ab0e47061d391169780447027188de354faba9aec422437d61e36a51f1e702a98674e466f26c4f9cbbd17773a3ec
6
+ metadata.gz: 5b2c33b0c3835ebac5f42bd58f90536da481cce4ab252b3295f1b6d727ba875ec2a0543fc3e28baa16500ae8cd6767d3fe1af41625afd9740017c5f982dd24b9
7
+ data.tar.gz: 10773603d25db7f0aaf99eea17926ab4b253378834900780f95627c4b71eeec7f87e9cbb515eee1151b7ff4b26b682ff9fa5a4a1f36c2ed3e8e88876c7b2c8d6
data/CHANGELOG.md CHANGED
@@ -8,6 +8,51 @@ This project mostly adheres to [Semantic Versioning](http://semver.org/).
8
8
  Unreleased
9
9
  ----------
10
10
 
11
+ ### Compatible changes
12
+
13
+ ### Breaking changes
14
+
15
+
16
+ 0.18.0
17
+ ------
18
+
19
+ ### Compatible changes
20
+
21
+ - New UJS attribute [`[up-toggle]`](/up-toggle) to show or hide part of a form if certain options are selected or boxes are checked.
22
+ - Links can now have an optional `up-confirm` attribute. This opens a confirmation dialog with the given message
23
+ before the link is followed or the modal/popup is opened.
24
+ - New function [`up.off`](/up.off). This unregisters an event listener previously bound with [`up.on`](/up.on).
25
+ - If a container contains more than one link, you can now set the value of the [`up-expand`](/up-expand)
26
+ attribute to a CSS selector to define which link should be expanded.
27
+ - You can now configure a list of idempotent HTTP methods in [`up.proxy.config.safeMethods`](/up.proxy.config).
28
+ The proxy cache will only cache idempotent requests and will clear the entire
29
+ cache after a non-idempotent request.
30
+ - Loading modals and popups will now open if there is a fragment update between the modal/popup's
31
+ request and response.
32
+ - [`up.follow`](/up.follow) and [`up.replace`](/up.replace) now have an option `{ failTarget }`.
33
+ Use it to define the selector to replace if the server responds with a non-200 status code.
34
+ - [`[up-target]`](/up-target) and [`up-follow`](/up.replace) now have a modifying attribute `up-fail-target`.
35
+ Use it to define the selector to replace if the server responds with a non-200 status code.
36
+ - New utility method [`up.util.reject`](/up.util.reject)
37
+ - New utility method [`up.util.only`](/up.util.only)
38
+ - New utility method [`up.util.except`](/up.util.except)
39
+ - Fix a bug where modals could no longer be opened on some browsers
40
+
41
+ ### Breaking changes
42
+
43
+ - By default Up.js now converts `PUT`, `PATCH` and `DELETE` requests to `POST` requests
44
+ that carry their original method in a form parameter named `_method`.
45
+ This is to [prevent unexpected redirect behavior](https://makandracards.com/makandra/38347).
46
+
47
+ Web frameworks like Ruby on Rails or Sinatra are aware of the `_method` parameter and use
48
+ its value as the method for routing.
49
+
50
+ You can configure this behavior in [`up.proxy.config.wrapMethods`](/up.proxy.config)
51
+ and [`up.proxy.config.wrapMethodParam`](/up.proxy.config).
52
+ - The requested selector is now sent to the server as a request header `X-Up-Target`
53
+ (this used to be `X-Up-Selector`). If you are using `upjs-rails`, you can access it
54
+ through `up.target` (this used to be `up.selector`).
55
+
11
56
 
12
57
  0.17.0
13
58
  ------
@@ -67,7 +112,7 @@ Unreleased
67
112
 
68
113
  ### Compatible changes
69
114
 
70
- - New function [`up.autosubmit`](/up.autosubmit) and select [`[up-autosubmit]`] to
115
+ - New function [`up.autosubmit`](/up.autosubmit) and selector [`[up-autosubmit]`](/up-autosubmit) to
71
116
  observe a form or field and submit the form when a value changes.
72
117
  - [`up.observe`](/up.observe) and [`[up-observe]`](/up-observe) can now be applied
73
118
  to `<form>` tags. The callback is run when any field in the form changes.
data/dist/up.js CHANGED
@@ -27,7 +27,7 @@ that might save you from loading something like [Underscore.js](http://underscor
27
27
  @function up.util.memoize
28
28
  @internal
29
29
  */
30
- var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, any, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, debug, detect, each, error, escapePressed, evalConsoleTemplate, extend, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, option, options, parseUrl, presence, presentAttr, remove, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, warn;
30
+ var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, any, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, debug, detect, each, error, escapePressed, evalConsoleTemplate, except, extend, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, intersect, isArray, isBlank, isDeferred, isDefined, isElement, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, nonUpClasses, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, option, options, parseUrl, presence, presentAttr, reject, remove, requestDataAsArray, requestDataAsQueryString, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, warn;
31
31
  memoize = function(func) {
32
32
  var cache, cached;
33
33
  cache = void 0;
@@ -44,19 +44,6 @@ that might save you from loading something like [Underscore.js](http://underscor
44
44
  };
45
45
  };
46
46
 
47
- /**
48
- @function up.util.ajax
49
- @internal
50
- */
51
- ajax = function(request) {
52
- request = copy(request);
53
- if (request.selector) {
54
- request.headers || (request.headers = {});
55
- request.headers['X-Up-Selector'] = request.selector;
56
- }
57
- return $.ajax(request);
58
- };
59
-
60
47
  /**
61
48
  Returns if the given port is the default port for the given protocol.
62
49
 
@@ -307,7 +294,7 @@ that might save you from loading something like [Underscore.js](http://underscor
307
294
  @experimental
308
295
  */
309
296
  selectorForElement = function(element) {
310
- var $element, classString, classes, id, j, klass, len, name, selector, upId;
297
+ var $element, classes, id, j, klass, len, name, selector, upId;
311
298
  $element = $(element);
312
299
  selector = void 0;
313
300
  debug("Creating selector from element %o", $element.get(0));
@@ -317,8 +304,8 @@ that might save you from loading something like [Underscore.js](http://underscor
317
304
  selector = "#" + id;
318
305
  } else if (name = presence($element.attr("name"))) {
319
306
  selector = "[name='" + name + "']";
320
- } else if (classString = presence($element.attr("class"))) {
321
- classes = classString.split(' ');
307
+ } else if (classes = presence(nonUpClasses($element))) {
308
+ console.log("using klass!", classes);
322
309
  selector = '';
323
310
  for (j = 0, len = classes.length; j < len; j++) {
324
311
  klass = classes[j];
@@ -329,6 +316,14 @@ that might save you from loading something like [Underscore.js](http://underscor
329
316
  }
330
317
  return selector;
331
318
  };
319
+ nonUpClasses = function($element) {
320
+ var classString, classes;
321
+ classString = $element.attr('class') || '';
322
+ classes = classString.split(' ');
323
+ return select(classes, function(klass) {
324
+ return isPresent(klass) && !klass.match(/^up-/);
325
+ });
326
+ };
332
327
  createElementFromHtml = function(html) {
333
328
  var anything, bodyElement, bodyMatch, bodyPattern, capture, closeTag, headElement, htmlElement, openTag, titleElement, titleMatch, titlePattern;
334
329
  openTag = function(tag) {
@@ -890,6 +885,35 @@ that might save you from loading something like [Underscore.js](http://underscor
890
885
  return matches;
891
886
  };
892
887
 
888
+ /**
889
+ Returns all elements from the given array that do not return
890
+ a truthy value when passed to the given function.
891
+
892
+ @function up.util.reject
893
+ @param {Array<T>} array
894
+ @return {Array<T>}
895
+ @stable
896
+ */
897
+ reject = function(array, tester) {
898
+ return select(array, function(element) {
899
+ return !tester(element);
900
+ });
901
+ };
902
+
903
+ /**
904
+ Returns the intersection of the given two arrays.
905
+
906
+ Implementation is not optimized. Don't use it for large arrays.
907
+
908
+ @function up.util.intersect
909
+ @internal
910
+ */
911
+ intersect = function(array1, array2) {
912
+ return select(array1, function(element) {
913
+ return contains(array2, element);
914
+ });
915
+ };
916
+
893
917
  /**
894
918
  Returns the first [present](/up.util.isPresent) element attribute
895
919
  among the given list of attribute names.
@@ -1299,6 +1323,26 @@ that might save you from loading something like [Underscore.js](http://underscor
1299
1323
  return filtered;
1300
1324
  };
1301
1325
 
1326
+ /**
1327
+ Returns a copy of the given object that contains all except
1328
+ the given properties.
1329
+
1330
+ @function up.util.except
1331
+ @param {Object} object
1332
+ @param {Array} keys...
1333
+ @stable
1334
+ */
1335
+ except = function() {
1336
+ var filtered, j, len, object, properties, property;
1337
+ object = arguments[0], properties = 2 <= arguments.length ? slice.call(arguments, 1) : [];
1338
+ filtered = copy(object);
1339
+ for (j = 0, len = properties.length; j < len; j++) {
1340
+ property = properties[j];
1341
+ delete filtered[property];
1342
+ }
1343
+ return filtered;
1344
+ };
1345
+
1302
1346
  /**
1303
1347
  @function up.util.isUnmodifiedKeyEvent
1304
1348
  @internal
@@ -1720,7 +1764,61 @@ that might save you from loading something like [Underscore.js](http://underscor
1720
1764
  bottom: ''
1721
1765
  });
1722
1766
  };
1767
+
1768
+ /**
1769
+ Normalizes the given params object to the form returned by
1770
+ [`jQuery.serializeArray`](https://api.jquery.com/serializeArray/).
1771
+
1772
+ @function up.util.requestDataAsArray
1773
+ @param {Object|Array|Undefined|Null} data
1774
+ @internal
1775
+ */
1776
+ requestDataAsArray = function(data) {
1777
+ var name, results, value;
1778
+ if (isMissing(data)) {
1779
+ return [];
1780
+ } else if (isArray(data)) {
1781
+ return data;
1782
+ } else if (isObject(data)) {
1783
+ results = [];
1784
+ for (name in data) {
1785
+ value = data[name];
1786
+ results.push({
1787
+ name: name,
1788
+ value: value
1789
+ });
1790
+ }
1791
+ return results;
1792
+ } else {
1793
+ return error('Unknown options.data type for %o', data);
1794
+ }
1795
+ };
1796
+
1797
+ /**
1798
+ Returns an URL-encoded query string for the given params object.
1799
+
1800
+ @function up.util.requestDataAsQueryString
1801
+ @param {Object|Array|Undefined|Null} data
1802
+ @internal
1803
+ */
1804
+ requestDataAsQueryString = function(data) {
1805
+ var array, query;
1806
+ array = requestDataAsArray(data);
1807
+ query = '';
1808
+ if (isPresent(array)) {
1809
+ query += '?';
1810
+ each(array, function(field, index) {
1811
+ if (index !== 0) {
1812
+ query += '&';
1813
+ }
1814
+ return query += encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value);
1815
+ });
1816
+ }
1817
+ return query;
1818
+ };
1723
1819
  return {
1820
+ requestDataAsArray: requestDataAsArray,
1821
+ requestDataAsQueryString: requestDataAsQueryString,
1724
1822
  offsetParent: offsetParent,
1725
1823
  fixedToAbsolute: fixedToAbsolute,
1726
1824
  presentAttr: presentAttr,
@@ -1731,7 +1829,6 @@ that might save you from loading something like [Underscore.js](http://underscor
1731
1829
  createElementFromHtml: createElementFromHtml,
1732
1830
  $createElementFromSelector: $createElementFromSelector,
1733
1831
  selectorForElement: selectorForElement,
1734
- ajax: ajax,
1735
1832
  extend: extend,
1736
1833
  copy: copy,
1737
1834
  merge: merge,
@@ -1746,6 +1843,8 @@ that might save you from loading something like [Underscore.js](http://underscor
1746
1843
  any: any,
1747
1844
  detect: detect,
1748
1845
  select: select,
1846
+ reject: reject,
1847
+ intersect: intersect,
1749
1848
  compact: compact,
1750
1849
  uniq: uniq,
1751
1850
  last: last,
@@ -1787,6 +1886,7 @@ that might save you from loading something like [Underscore.js](http://underscor
1787
1886
  methodFromXhr: methodFromXhr,
1788
1887
  clientSize: clientSize,
1789
1888
  only: only,
1889
+ except: except,
1790
1890
  trim: trim,
1791
1891
  unresolvableDeferred: unresolvableDeferred,
1792
1892
  unresolvablePromise: unresolvablePromise,
@@ -1827,32 +1927,42 @@ we can't currently get rid off.
1827
1927
  var slice = [].slice;
1828
1928
 
1829
1929
  up.browser = (function($) {
1830
- var canCssTransition, canInputEvent, canLogSubstitution, canPushState, initialRequestMethod, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, u, url;
1930
+ var canCssTransition, canInputEvent, canLogSubstitution, canPushState, confirm, initialRequestMethod, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, u, url;
1831
1931
  u = up.util;
1932
+
1933
+ /**
1934
+ @method up.browser.loadPage
1935
+ @param {String} url
1936
+ @param {String} [options.method='get']
1937
+ @param {Object|Array} [options.data]
1938
+ @internal
1939
+ */
1832
1940
  loadPage = function(url, options) {
1833
- var $form, csrfParam, csrfToken, metadataInput, method, target;
1941
+ var $form, addField, csrfField, method;
1834
1942
  if (options == null) {
1835
1943
  options = {};
1836
1944
  }
1837
1945
  method = u.option(options.method, 'get').toLowerCase();
1838
1946
  if (method === 'get') {
1839
- return location.href = url;
1840
- } else if ($.rails) {
1841
- target = options.target;
1842
- csrfToken = $.rails.csrfToken();
1843
- csrfParam = $.rails.csrfParam();
1947
+ return location.href = url + u.requestDataAsQueryString(options.data);
1948
+ } else {
1844
1949
  $form = $("<form method='post' action='" + url + "'></form>");
1845
- metadataInput = "<input name='_method' value='" + method + "' type='hidden' />";
1846
- if (u.isDefined(csrfParam) && u.isDefined(csrfToken)) {
1847
- metadataInput += "<input name='" + csrfParam + "' value='" + csrfToken + "' type='hidden' />";
1848
- }
1849
- if (target) {
1850
- $form.attr('target', target);
1950
+ addField = function(field) {
1951
+ var $field;
1952
+ $field = $('<input type="hidden">');
1953
+ $field.attr(field.name, field.value);
1954
+ return $field.appendTo($form);
1955
+ };
1956
+ addField({
1957
+ name: up.proxy.config.wrapMethodParam,
1958
+ value: method
1959
+ });
1960
+ if (csrfField = up.rails.csrfField()) {
1961
+ addField(csrfField);
1851
1962
  }
1852
- $form.hide().append(metadataInput).appendTo('body');
1963
+ u.each(u.requestDataAsArray(options.data), addField);
1964
+ $form.hide().appendTo('body');
1853
1965
  return $form.submit();
1854
- } else {
1855
- return error("Can't fake a " + (method.toUpperCase()) + " request without Rails UJS");
1856
1966
  }
1857
1967
  };
1858
1968
 
@@ -1945,7 +2055,7 @@ we can't currently get rid off.
1945
2055
  console.log("Hello %o!", "Judy");
1946
2056
 
1947
2057
  @function up.browser.canLogSubstitution
1948
- @return boolean
2058
+ @return {Boolean}
1949
2059
  @internal
1950
2060
  */
1951
2061
  canLogSubstitution = u.memoize(function() {
@@ -1967,6 +2077,19 @@ we can't currently get rid off.
1967
2077
  }
1968
2078
  return value;
1969
2079
  };
2080
+
2081
+ /**
2082
+ @function up,browser.confirm
2083
+ @return {Promise}
2084
+ @internal
2085
+ */
2086
+ confirm = function(message) {
2087
+ if (u.isBlank(message) || confirm(message)) {
2088
+ return u.resolvedPromise();
2089
+ } else {
2090
+ return u.unresolvablePromise();
2091
+ }
2092
+ };
1970
2093
  initialRequestMethod = u.memoize(function() {
1971
2094
  return (popCookie('_up_request_method') || 'get').toLowerCase();
1972
2095
  });
@@ -1993,6 +2116,7 @@ we can't currently get rid off.
1993
2116
  return {
1994
2117
  url: url,
1995
2118
  loadPage: loadPage,
2119
+ confirm: confirm,
1996
2120
  canPushState: canPushState,
1997
2121
  canCssTransition: canCssTransition,
1998
2122
  canInputEvent: canInputEvent,
@@ -2049,14 +2173,17 @@ and call `preventDefault()` on the `event` object:
2049
2173
  var slice = [].slice;
2050
2174
 
2051
2175
  up.bus = (function($) {
2052
- var boot, defaultLiveDescriptions, emit, emitReset, live, liveDescriptions, nobodyPrevents, onEscape, restoreSnapshot, snapshot, u, upListenerToJqueryListener;
2176
+ var boot, emit, emitReset, forgetUpDescription, live, liveUpDescriptions, nextUpDescriptionNumber, nobodyPrevents, onEscape, rememberUpDescription, restoreSnapshot, snapshot, u, unbind, upDescriptionNumber, upDescriptionToJqueryDescription, upListenerToJqueryListener;
2053
2177
  u = up.util;
2054
- liveDescriptions = [];
2055
- defaultLiveDescriptions = null;
2178
+ liveUpDescriptions = {};
2179
+ nextUpDescriptionNumber = 0;
2056
2180
 
2057
2181
  /**
2058
- * Convert an Up.js style listener (second argument is the event target
2059
- * as a jQuery collection) to a vanilla jQuery listener
2182
+ Convert an Up.js style listener (second argument is the event target
2183
+ as a jQuery collection) to a vanilla jQuery listener
2184
+
2185
+ @function upListenerToJqueryListener
2186
+ @internal
2060
2187
  */
2061
2188
  upListenerToJqueryListener = function(upListener) {
2062
2189
  return function(event) {
@@ -2066,6 +2193,30 @@ and call `preventDefault()` on the `event` object:
2066
2193
  };
2067
2194
  };
2068
2195
 
2196
+ /**
2197
+ Converts an argument list for `up.on` to an argument list for `jQuery.on`.
2198
+ This involves rewriting the listener signature in the last argument slot.
2199
+
2200
+ @function upDescriptionToJqueryDescription
2201
+ @internal
2202
+ */
2203
+ upDescriptionToJqueryDescription = function(upDescription, isNew) {
2204
+ var jqueryDescription, jqueryListener, upListener;
2205
+ jqueryDescription = u.copy(upDescription);
2206
+ upListener = jqueryDescription.pop();
2207
+ jqueryListener = void 0;
2208
+ if (isNew) {
2209
+ jqueryListener = upListenerToJqueryListener(upListener);
2210
+ upListener._asJqueryListener = jqueryListener;
2211
+ upListener._descriptionNumber = ++nextUpDescriptionNumber;
2212
+ } else {
2213
+ jqueryListener = upListener._asJqueryListener;
2214
+ jqueryListener || u.error('up.off: The event listener %o was never registered through up.on');
2215
+ }
2216
+ jqueryDescription.push(jqueryListener);
2217
+ return jqueryDescription;
2218
+ };
2219
+
2069
2220
  /**
2070
2221
  Listens to an event on `document`.
2071
2222
 
@@ -2085,7 +2236,6 @@ and call `preventDefault()` on the `event` object:
2085
2236
  Other than jQuery, Up.js will silently discard event listeners
2086
2237
  on [unsupported browsers](/up.browser.isSupported).
2087
2238
 
2088
-
2089
2239
  \#\#\#\# Attaching structured data
2090
2240
 
2091
2241
  In case you want to attach structured data to the event you're observing,
@@ -2100,7 +2250,6 @@ and call `preventDefault()` on the `event` object:
2100
2250
  console.log("This is %o who is %o years old", data.name, data.age);
2101
2251
  });
2102
2252
 
2103
-
2104
2253
  \#\#\#\# Migrating jQuery event handlers to `up.on`
2105
2254
 
2106
2255
  Within the event handler, Up.js will bind `this` to the
@@ -2119,6 +2268,12 @@ and call `preventDefault()` on the `event` object:
2119
2268
  $(this).something();
2120
2269
  });
2121
2270
 
2271
+ \#\#\#\# Stopping to listen
2272
+
2273
+ `up.on` returns a function that unbinds the event listeners when called.
2274
+
2275
+ There is also a function [`up.off`](/up.off) which you can use for the same purpose.
2276
+
2122
2277
  @function up.on
2123
2278
  @param {String} events
2124
2279
  A space-separated list of event names to bind.
@@ -2136,23 +2291,60 @@ and call `preventDefault()` on the `event` object:
2136
2291
  @stable
2137
2292
  */
2138
2293
  live = function() {
2139
- var $document, args, behavior, description, lastIndex;
2140
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2294
+ var jqueryDescription, ref, upDescription;
2295
+ upDescription = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2141
2296
  if (!up.browser.isSupported()) {
2142
2297
  return (function() {});
2143
2298
  }
2144
- description = u.copy(args);
2145
- lastIndex = description.length - 1;
2146
- behavior = description[lastIndex];
2147
- description[lastIndex] = upListenerToJqueryListener(behavior);
2148
- liveDescriptions.push(description);
2149
- $document = $(document);
2150
- $document.on.apply($document, description);
2299
+ jqueryDescription = upDescriptionToJqueryDescription(upDescription, true);
2300
+ rememberUpDescription(upDescription);
2301
+ (ref = $(document)).on.apply(ref, jqueryDescription);
2151
2302
  return function() {
2152
- return $document.off.apply($document, description);
2303
+ return unbind.apply(null, upDescription);
2153
2304
  };
2154
2305
  };
2155
2306
 
2307
+ /**
2308
+ Unregisters an event listener previously bound with [`up.on`](/up.on).
2309
+
2310
+ \#\#\#\# Example
2311
+
2312
+ Let's say you are listing to clicks on `.button` elements:
2313
+
2314
+ var listener = function() { };
2315
+ up.on('click', '.button', listener);
2316
+
2317
+ You can stop listening to these events like this:
2318
+
2319
+ up.off('click', '.button', listener);
2320
+
2321
+ Note that you need to pass `up.off` a reference to the same listener function
2322
+ that was passed to `up.on` earlier.
2323
+
2324
+ @function up.off
2325
+ @stable
2326
+ */
2327
+ unbind = function() {
2328
+ var jqueryDescription, ref, upDescription;
2329
+ upDescription = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2330
+ jqueryDescription = upDescriptionToJqueryDescription(upDescription, false);
2331
+ forgetUpDescription(upDescription);
2332
+ return (ref = $(document)).off.apply(ref, jqueryDescription);
2333
+ };
2334
+ rememberUpDescription = function(upDescription) {
2335
+ var number;
2336
+ number = upDescriptionNumber(upDescription);
2337
+ return liveUpDescriptions[number] = upDescription;
2338
+ };
2339
+ forgetUpDescription = function(upDescription) {
2340
+ var number;
2341
+ number = upDescriptionNumber(upDescription);
2342
+ return delete liveUpDescriptions[number];
2343
+ };
2344
+ upDescriptionNumber = function(upDescription) {
2345
+ return u.last(upDescription)._descriptionNumber;
2346
+ };
2347
+
2156
2348
  /**
2157
2349
  Emits a event with the given name and properties.
2158
2350
 
@@ -2179,7 +2371,7 @@ and call `preventDefault()` on the `event` object:
2179
2371
  will by default include properties like `preventDefault()`
2180
2372
  or `stopPropagation()`.
2181
2373
  @param {jQuery} [eventProps.$element=$(document)]
2182
- The element on which the event is trigered.
2374
+ The element on which the event is triggered.
2183
2375
  @experimental
2184
2376
  */
2185
2377
  emit = function(eventName, eventProps) {
@@ -2236,7 +2428,13 @@ and call `preventDefault()` on the `event` object:
2236
2428
  @internal
2237
2429
  */
2238
2430
  snapshot = function() {
2239
- return defaultLiveDescriptions = u.copy(liveDescriptions);
2431
+ var description, i, len, results;
2432
+ results = [];
2433
+ for (i = 0, len = liveUpDescriptions.length; i < len; i++) {
2434
+ description = liveUpDescriptions[i];
2435
+ results.push(description._isDefault = true);
2436
+ }
2437
+ return results;
2240
2438
  };
2241
2439
 
2242
2440
  /**
@@ -2246,14 +2444,16 @@ and call `preventDefault()` on the `event` object:
2246
2444
  @internal
2247
2445
  */
2248
2446
  restoreSnapshot = function() {
2249
- var description, i, len, ref;
2250
- for (i = 0, len = liveDescriptions.length; i < len; i++) {
2251
- description = liveDescriptions[i];
2252
- if (!u.contains(defaultLiveDescriptions, description)) {
2253
- (ref = $(document)).off.apply(ref, description);
2254
- }
2447
+ var description, doomedDescriptions, i, len, results;
2448
+ doomedDescriptions = u.reject(liveUpDescriptions, function(description) {
2449
+ return description._isDefault;
2450
+ });
2451
+ results = [];
2452
+ for (i = 0, len = doomedDescriptions.length; i < len; i++) {
2453
+ description = doomedDescriptions[i];
2454
+ results.push(unbind.apply(null, description));
2255
2455
  }
2256
- return liveDescriptions = u.copy(defaultLiveDescriptions);
2456
+ return results;
2257
2457
  };
2258
2458
 
2259
2459
  /**
@@ -2306,7 +2506,9 @@ and call `preventDefault()` on the `event` object:
2306
2506
  live('up:framework:boot', snapshot);
2307
2507
  live('up:framework:reset', restoreSnapshot);
2308
2508
  return {
2509
+ knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
2309
2510
  on: live,
2511
+ off: unbind,
2310
2512
  emit: emit,
2311
2513
  nobodyPrevents: nobodyPrevents,
2312
2514
  onEscape: onEscape,
@@ -2317,6 +2519,8 @@ and call `preventDefault()` on the `event` object:
2317
2519
 
2318
2520
  up.on = up.bus.on;
2319
2521
 
2522
+ up.off = up.bus.off;
2523
+
2320
2524
  up.emit = up.bus.emit;
2321
2525
 
2322
2526
  up.reset = up.bus.emitReset;
@@ -2692,11 +2896,11 @@ later.
2692
2896
  up.on('ready', (function() {
2693
2897
  return hello(document.body);
2694
2898
  }));
2695
- up.on('up:fragment:inserted', function(event) {
2696
- return compile(event.$element);
2899
+ up.on('up:fragment:inserted', function(event, $element) {
2900
+ return compile($element);
2697
2901
  });
2698
- up.on('up:fragment:destroy', function(event) {
2699
- return runDestroyers(event.$element);
2902
+ up.on('up:fragment:destroy', function(event, $element) {
2903
+ return runDestroyers($element);
2700
2904
  });
2701
2905
  up.on('up:framework:boot', snapshot);
2702
2906
  up.on('up:framework:reset', reset);
@@ -3561,7 +3765,7 @@ are based on this module.
3561
3765
 
3562
3766
  (function() {
3563
3767
  up.flow = (function($) {
3564
- var autofocus, destroy, elementsInserted, findOldFragment, first, implant, isRealElement, oldFragmentNotFound, parseImplantSteps, parseResponse, reload, replace, resolveSelector, setSource, source, swapElements, u;
3768
+ var autofocus, destroy, elementsInserted, findOldFragment, first, implant, isRealElement, oldFragmentNotFound, parseImplantSteps, parseResponse, processResponse, reload, replace, resolveSelector, setSource, source, swapElements, u;
3565
3769
  u = up.util;
3566
3770
  setSource = function(element, sourceUrl) {
3567
3771
  var $element;
@@ -3571,6 +3775,14 @@ are based on this module.
3571
3775
  }
3572
3776
  return $element.attr("up-source", sourceUrl);
3573
3777
  };
3778
+
3779
+ /**
3780
+ Returns the URL the given element was retrieved from.
3781
+
3782
+ @method up.flow.source
3783
+ @param {String|Element|jQuery} selectorOrElement
3784
+ @experimental
3785
+ */
3574
3786
  source = function(selectorOrElement) {
3575
3787
  var $element;
3576
3788
  $element = $(selectorOrElement).closest('[up-source]');
@@ -3578,15 +3790,22 @@ are based on this module.
3578
3790
  };
3579
3791
 
3580
3792
  /**
3793
+ Resolves the given selector (which might contain `&` references)
3794
+ to an absolute selector.
3795
+
3581
3796
  @function up.flow.resolveSelector
3797
+ @param {String|Element|jQuery} selectorOrElement
3798
+ @param {String|Element|jQuery} origin
3799
+ The element that this selector resolution is relative to.
3800
+ That element's selector will be substituted for `&`.
3582
3801
  @internal
3583
3802
  */
3584
- resolveSelector = function(selectorOrElement, options) {
3585
- var origin, originSelector, selector;
3803
+ resolveSelector = function(selectorOrElement, origin) {
3804
+ var originSelector, selector;
3586
3805
  if (u.isString(selectorOrElement)) {
3587
3806
  selector = selectorOrElement;
3588
3807
  if (u.contains(selector, '&')) {
3589
- if (origin = u.presence(options.origin)) {
3808
+ if (origin) {
3590
3809
  originSelector = u.selectorForElement(origin);
3591
3810
  selector = selector.replace(/\&/, originSelector);
3592
3811
  } else {
@@ -3662,7 +3881,7 @@ are based on this module.
3662
3881
  \#\#\#\# Optimizing response rendering
3663
3882
 
3664
3883
  The server is free to optimize Up.js requests by only rendering the HTML fragment
3665
- that is being updated. The request's `X-Up-Selector` header will contain
3884
+ that is being updated. The request's `X-Up-Target` header will contain
3666
3885
  the CSS selector for the updating fragment.
3667
3886
 
3668
3887
  If you are using the `upjs-rails` gem you can also access the selector via
@@ -3680,8 +3899,17 @@ are based on this module.
3680
3899
  here, in which case a selector will be inferred from the element's class and ID.
3681
3900
  @param {String} url
3682
3901
  The URL to fetch from the server.
3683
- @param {String} [options.method='get']
3902
+ @param {String} [options.failTarget='body']
3903
+ The CSS selector to update if the server sends a non-200 status code.
3684
3904
  @param {String} [options.title]
3905
+ @param {String} [options.method='get']
3906
+ @param {Object|Array} [options.data]
3907
+ Parameters that should be sent as the request's payload.
3908
+
3909
+ Parameters can either be passed as an object (where the property names become
3910
+ the param names and the property values become the param values) or as
3911
+ an array of `{ name: 'param-name', value: 'param-value' }` objects
3912
+ (compare to jQuery's [`serializeArray`](https://api.jquery.com/serializeArray/)).
3685
3913
  @param {String} [options.transition='none']
3686
3914
  @param {String|Boolean} [options.history=true]
3687
3915
  If a `String` is given, it is used as the URL the browser's location bar and history.
@@ -3713,50 +3941,95 @@ are based on this module.
3713
3941
  @stable
3714
3942
  */
3715
3943
  replace = function(selectorOrElement, url, options) {
3716
- var promise, request, selector;
3944
+ var failTarget, promise, request, target;
3717
3945
  u.debug("Replace %o with %o (options %o)", selectorOrElement, url, options);
3718
3946
  options = u.options(options);
3719
- selector = resolveSelector(selectorOrElement, options);
3947
+ target = resolveSelector(selectorOrElement, options.origin);
3948
+ failTarget = u.option(options.failTarget, 'body');
3949
+ failTarget = resolveSelector(failTarget, options.origin);
3720
3950
  if (!up.browser.canPushState() && options.history !== false) {
3721
3951
  if (!options.preload) {
3722
- up.browser.loadPage(url, u.only(options, 'method'));
3952
+ up.browser.loadPage(url, u.only(options, 'method', 'data'));
3723
3953
  }
3724
3954
  return u.unresolvablePromise();
3725
3955
  }
3726
3956
  request = {
3727
3957
  url: url,
3728
3958
  method: options.method,
3729
- selector: selector,
3959
+ data: options.data,
3960
+ target: target,
3961
+ failTarget: failTarget,
3730
3962
  cache: options.cache,
3731
3963
  preload: options.preload,
3732
3964
  headers: options.headers
3733
3965
  };
3734
3966
  promise = up.proxy.ajax(request);
3735
3967
  promise.done(function(html, textStatus, xhr) {
3736
- var currentLocation, newRequest;
3737
- if (currentLocation = u.locationFromXhr(xhr)) {
3738
- u.debug('Location from server: %o', currentLocation);
3968
+ return processResponse(true, target, url, request, xhr, options);
3969
+ });
3970
+ promise.fail(function(xhr, textStatus, errorThrown) {
3971
+ return processResponse(false, failTarget, url, request, xhr, options);
3972
+ });
3973
+ return promise;
3974
+ };
3975
+
3976
+ /**
3977
+ @internal
3978
+ */
3979
+ processResponse = function(isSuccess, selector, url, request, xhr, options) {
3980
+ var isReloadable, newRequest, urlFromServer;
3981
+ options.method = u.normalizeMethod(u.option(u.methodFromXhr(xhr), options.method));
3982
+ options.title = u.option(u.titleFromXhr(xhr), options.title);
3983
+ isReloadable = options.method === 'GET';
3984
+ if (urlFromServer = u.locationFromXhr(xhr)) {
3985
+ url = urlFromServer;
3986
+ if (isSuccess) {
3739
3987
  newRequest = {
3740
- url: currentLocation,
3988
+ url: url,
3741
3989
  method: u.methodFromXhr(xhr),
3742
- selector: selector
3990
+ target: selector
3743
3991
  };
3744
3992
  up.proxy.alias(request, newRequest);
3745
- url = currentLocation;
3746
- }
3747
- if (options.history !== false) {
3748
- options.history = url;
3749
3993
  }
3750
- if (options.source !== false) {
3751
- options.source = url;
3994
+ } else if (isReloadable) {
3995
+ url = url + u.requestDataAsQueryString(options.data);
3996
+ }
3997
+ if (isSuccess) {
3998
+ if (isReloadable) {
3999
+ if (!(options.history === false || u.isString(options.history))) {
4000
+ options.history = url;
4001
+ }
4002
+ if (!(options.source === false || u.isString(options.source))) {
4003
+ options.source = url;
4004
+ }
4005
+ } else {
4006
+ if (!u.isString(options.history)) {
4007
+ options.history = false;
4008
+ }
4009
+ if (!u.isString(options.source)) {
4010
+ options.source = 'keep';
4011
+ }
3752
4012
  }
3753
- options.title || (options.title = u.titleFromXhr(xhr));
3754
- if (!options.preload) {
3755
- return implant(selector, html, options);
4013
+ } else {
4014
+ options.transition = options.failTransition;
4015
+ options.failTransition = void 0;
4016
+ if (isReloadable) {
4017
+ if (options.history !== false) {
4018
+ options.history = url;
4019
+ }
4020
+ if (options.source !== false) {
4021
+ options.source = url;
4022
+ }
4023
+ } else {
4024
+ options.source = 'keep';
4025
+ options.history = false;
3756
4026
  }
3757
- });
3758
- promise.fail(u.error);
3759
- return promise;
4027
+ }
4028
+ if (options.preload) {
4029
+ return u.resolvedPromise();
4030
+ } else {
4031
+ return implant(selector, xhr.responseText, options);
4032
+ }
3760
4033
  };
3761
4034
 
3762
4035
  /**
@@ -3792,34 +4065,41 @@ are based on this module.
3792
4065
  @param {String} html
3793
4066
  @param {Object} [options]
3794
4067
  See options for [`up.replace`](/up.replace).
4068
+ @return {Promise}
4069
+ A promise that will be resolved then the selector was updated
4070
+ and all animation has finished.
3795
4071
  @experimental
3796
4072
  */
3797
4073
  implant = function(selectorOrElement, html, options) {
3798
- var $new, $old, j, len, ref, ref1, response, results, selector, step;
3799
- selector = resolveSelector(selectorOrElement, options);
4074
+ var $new, $old, deferred, deferreds, j, len, ref, ref1, ref2, response, selector, step;
3800
4075
  options = u.options(options, {
3801
4076
  historyMethod: 'push',
3802
4077
  requireMatch: true
3803
4078
  });
3804
- options.source = u.option(options.source, options.history);
4079
+ selector = resolveSelector(selectorOrElement, options.origin);
3805
4080
  response = parseResponse(html, options);
3806
4081
  options.title || (options.title = response.title());
3807
4082
  if (options.saveScroll !== false) {
3808
4083
  up.layout.saveScroll();
3809
4084
  }
4085
+ if (typeof options.beforeSwap === "function") {
4086
+ options.beforeSwap($old, $new);
4087
+ }
4088
+ deferreds = [];
3810
4089
  ref = parseImplantSteps(selector, options);
3811
- results = [];
3812
4090
  for (j = 0, len = ref.length; j < len; j++) {
3813
4091
  step = ref[j];
3814
4092
  $old = findOldFragment(step.selector, options);
3815
4093
  $new = (ref1 = response.find(step.selector)) != null ? ref1.first() : void 0;
3816
4094
  if ($old && $new) {
3817
- results.push(swapElements($old, $new, step.pseudoClass, step.transition, options));
3818
- } else {
3819
- results.push(void 0);
4095
+ deferred = swapElements($old, $new, step.pseudoClass, step.transition, options);
4096
+ deferreds.push(deferred);
3820
4097
  }
3821
4098
  }
3822
- return results;
4099
+ if (typeof options.afterSwap === "function") {
4100
+ options.afterSwap($old, $new);
4101
+ }
4102
+ return (ref2 = up.motion).when.apply(ref2, deferreds);
3823
4103
  };
3824
4104
  findOldFragment = function(selector, options) {
3825
4105
  return first(".up-popup " + selector) || first(".up-modal " + selector) || first(selector) || oldFragmentNotFound(selector, options);
@@ -3868,8 +4148,13 @@ are based on this module.
3868
4148
  });
3869
4149
  };
3870
4150
  swapElements = function($old, $new, pseudoClass, transition, options) {
3871
- var $wrapper, insertionMethod;
4151
+ var $wrapper, insertionMethod, promise, replacement;
3872
4152
  transition || (transition = 'none');
4153
+ if (options.source === 'keep') {
4154
+ options = u.merge(options, {
4155
+ source: source($old)
4156
+ });
4157
+ }
3873
4158
  up.motion.finish($old);
3874
4159
  if (pseudoClass) {
3875
4160
  insertionMethod = pseudoClass === 'before' ? 'prepend' : 'append';
@@ -3877,21 +4162,25 @@ are based on this module.
3877
4162
  $old[insertionMethod]($wrapper);
3878
4163
  u.copyAttributes($new, $old);
3879
4164
  elementsInserted($wrapper.children(), options);
3880
- return up.layout.revealOrRestoreScroll($wrapper, options).then(function() {
4165
+ promise = up.layout.revealOrRestoreScroll($wrapper, options);
4166
+ promise = promise.then(function() {
3881
4167
  return up.animate($wrapper, transition, options);
3882
- }).then(function() {
3883
- u.unwrapElement($wrapper);
3884
4168
  });
4169
+ promise = promise.then(function() {
4170
+ return u.unwrapElement($wrapper);
4171
+ });
4172
+ return promise;
3885
4173
  } else {
3886
- return destroy($old, {
3887
- animation: function() {
3888
- $new.insertBefore($old);
3889
- elementsInserted($new, options);
3890
- if ($old.is('body') && transition !== 'none') {
3891
- u.error('Cannot apply transitions to body-elements (%o)', transition);
3892
- }
3893
- return up.morph($old, $new, transition, options);
4174
+ replacement = function() {
4175
+ $new.insertBefore($old);
4176
+ elementsInserted($new, options);
4177
+ if ($old.is('body') && transition !== 'none') {
4178
+ u.error('Cannot apply transitions to body-elements (%o)', transition);
3894
4179
  }
4180
+ return up.morph($old, $new, transition, options);
4181
+ };
4182
+ return destroy($old, {
4183
+ animation: replacement
3895
4184
  });
3896
4185
  }
3897
4186
  };
@@ -3907,6 +4196,7 @@ are based on this module.
3907
4196
  for (i = j = 0, len = disjunction.length; j < len; i = ++j) {
3908
4197
  selectorAtom = disjunction[i];
3909
4198
  selectorParts = selectorAtom.match(/^(.+?)(?:\:(before|after))?$/);
4199
+ selectorParts || u.error('Could not parse selector atom %o', selectorAtom);
3910
4200
  selector = selectorParts[1];
3911
4201
  if (selector === 'html') {
3912
4202
  selector = 'body';
@@ -4006,7 +4296,7 @@ are based on this module.
4006
4296
  $element: $element
4007
4297
  })) {
4008
4298
  options = u.options(options, {
4009
- animation: 'none'
4299
+ animation: false
4010
4300
  });
4011
4301
  animateOptions = up.motion.animateOptions(options);
4012
4302
  $element.addClass('up-destroying');
@@ -4090,11 +4380,13 @@ are based on this module.
4090
4380
  return setSource(document.body, up.browser.url());
4091
4381
  });
4092
4382
  return {
4383
+ knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
4093
4384
  replace: replace,
4094
4385
  reload: reload,
4095
4386
  destroy: destroy,
4096
4387
  implant: implant,
4097
4388
  first: first,
4389
+ source: source,
4098
4390
  resolveSelector: resolveSelector
4099
4391
  };
4100
4392
  })(jQuery);
@@ -4272,9 +4564,8 @@ or [transitions](/up.transition) using Javascript or CSS.
4272
4564
  finish($element);
4273
4565
  options = animateOptions(options);
4274
4566
  if (animation === 'none' || animation === false) {
4275
- none();
4276
- }
4277
- if (u.isFunction(animation)) {
4567
+ return none();
4568
+ } else if (u.isFunction(animation)) {
4278
4569
  return assertIsDeferred(animation($element, options), animation);
4279
4570
  } else if (u.isString(animation)) {
4280
4571
  return animate($element, findAnimation(animation), options);
@@ -4286,7 +4577,7 @@ or [transitions](/up.transition) using Javascript or CSS.
4286
4577
  return u.resolvedDeferred();
4287
4578
  }
4288
4579
  } else {
4289
- return u.error("Unknown animation type %o", animation);
4580
+ return u.error("Unknown animation type for %o", animation);
4290
4581
  }
4291
4582
  };
4292
4583
 
@@ -4453,7 +4744,7 @@ or [transitions](/up.transition) using Javascript or CSS.
4453
4744
  @stable
4454
4745
  */
4455
4746
  morph = function(source, target, transitionOrName, options) {
4456
- var $new, $old, animation, deferred, parsedOptions, parts, transition;
4747
+ var $new, $old, animation, parsedOptions, parts, transition;
4457
4748
  u.debug('Morphing %o to %o (using %o)', source, target, transitionOrName);
4458
4749
  $old = $(source);
4459
4750
  $new = $(target);
@@ -4462,12 +4753,11 @@ or [transitions](/up.transition) using Javascript or CSS.
4462
4753
  if (isEnabled()) {
4463
4754
  finish($old);
4464
4755
  finish($new);
4465
- if (transitionOrName === 'none' || transitionOrName === false || (animation = animations[transitionOrName])) {
4466
- deferred = skipMorph($old, $new, parsedOptions);
4467
- deferred.then(function() {
4468
- return animate($new, animation || 'none', options);
4469
- });
4470
- return deferred;
4756
+ if (transitionOrName === 'none' || transitionOrName === false) {
4757
+ return skipMorph($old, $new, parsedOptions);
4758
+ } else if (animation = animations[transitionOrName]) {
4759
+ skipMorph($old, $new, parsedOptions);
4760
+ return animate($new, animation, parsedOptions);
4471
4761
  } else if (transition = u.presence(transitionOrName, u.isFunction) || transitions[transitionOrName]) {
4472
4762
  return withGhosts($old, $new, parsedOptions, function($oldGhost, $newGhost) {
4473
4763
  var transitionPromise;
@@ -4871,7 +5161,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
4871
5161
  var slice = [].slice;
4872
5162
 
4873
5163
  up.proxy = (function($) {
4874
- var $waitingLink, SAFE_HTTP_METHODS, ajax, alias, busy, busyDelayTimer, busyEventEmitted, cache, cacheKey, cancelBusyDelay, cancelPreloadDelay, checkPreload, clear, config, get, idle, isIdempotent, load, loadEnded, loadOrQueue, loadStarted, normalizeRequest, pendingCount, pokeQueue, preload, preloadDelayTimer, queue, queuedRequests, remove, reset, set, startPreloadDelay, u;
5164
+ var $waitingLink, ajax, alias, busy, busyDelayTimer, busyEventEmitted, cache, cacheKey, cancelBusyDelay, cancelPreloadDelay, checkPreload, clear, config, get, idle, isIdempotent, load, loadEnded, loadOrQueue, loadStarted, normalizeRequest, pendingCount, pokeQueue, preload, preloadDelayTimer, queue, queuedRequests, remove, reset, set, startPreloadDelay, u;
4875
5165
  u = up.util;
4876
5166
  $waitingLink = void 0;
4877
5167
  preloadDelayTimer = void 0;
@@ -4903,6 +5193,16 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
4903
5193
 
4904
5194
  Note that your browser might [impose its own request limit](http://www.browserscope.org/?category=network)
4905
5195
  regardless of what you configure here.
5196
+ @param {Array<String>} [config.wrapMethods]
5197
+ An array of uppercase HTTP method names. AJAX requests with one of these methods
5198
+ will be converted into a `POST` request and carry their original method as a `_method`
5199
+ parameter. This is to [prevent unexpected redirect behavior](https://makandracards.com/makandra/38347).
5200
+ @param {String} [config.wrapMethodParam]
5201
+ The name of the POST parameter when wrapping HTTP methods in a `POST` request.
5202
+ @param {Array<String>} [config.safeMethods]
5203
+ An array of uppercase HTTP method names that are considered idempotent.
5204
+ The proxy cache will only cache idempotent requests and will clear the entire
5205
+ cache after a non-idempotent request.
4906
5206
  @stable
4907
5207
  */
4908
5208
  config = u.config({
@@ -4910,11 +5210,14 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
4910
5210
  preloadDelay: 75,
4911
5211
  cacheSize: 70,
4912
5212
  cacheExpiry: 1000 * 60 * 5,
4913
- maxRequests: 4
5213
+ maxRequests: 4,
5214
+ wrapMethods: ['PATCH', 'PUT', 'DELETE'],
5215
+ wrapMethodParam: '_method',
5216
+ safeMethods: ['GET', 'OPTIONS', 'HEAD']
4914
5217
  });
4915
5218
  cacheKey = function(request) {
4916
5219
  normalizeRequest(request);
4917
- return [request.url, request.method, request.data, request.selector].join('|');
5220
+ return [request.url, request.method, request.data, request.target].join('|');
4918
5221
  };
4919
5222
  cache = u.cache({
4920
5223
  size: function() {
@@ -4942,14 +5245,14 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
4942
5245
  var candidate, candidates, i, len, requestForBody, requestForHtml, response;
4943
5246
  request = normalizeRequest(request);
4944
5247
  candidates = [request];
4945
- if (request.selector !== 'html') {
5248
+ if (request.target !== 'html') {
4946
5249
  requestForHtml = u.merge(request, {
4947
- selector: 'html'
5250
+ target: 'html'
4948
5251
  });
4949
5252
  candidates.push(requestForHtml);
4950
- if (request.selector !== 'body') {
5253
+ if (request.target !== 'body') {
4951
5254
  requestForBody = u.merge(request, {
4952
- selector: 'body'
5255
+ target: 'body'
4953
5256
  });
4954
5257
  candidates.push(requestForBody);
4955
5258
  }
@@ -4968,7 +5271,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
4968
5271
  @function up.proxy.set
4969
5272
  @param {String} request.url
4970
5273
  @param {String} [request.method='GET']
4971
- @param {String} [request.selector='body']
5274
+ @param {String} [request.target='body']
4972
5275
  @param {Promise} response
4973
5276
  A promise for the response that is API-compatible with the
4974
5277
  promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
@@ -4985,7 +5288,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
4985
5288
  @function up.proxy.remove
4986
5289
  @param {String} request.url
4987
5290
  @param {String} [request.method='GET']
4988
- @param {String} [request.selector='body']
5291
+ @param {String} [request.target='body']
4989
5292
  @experimental
4990
5293
  */
4991
5294
  remove = cache.remove;
@@ -5026,7 +5329,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5026
5329
  if (request.url) {
5027
5330
  request.url = u.normalizeUrl(request.url);
5028
5331
  }
5029
- request.selector || (request.selector = 'body');
5332
+ request.target || (request.target = 'body');
5030
5333
  request._normalized = true;
5031
5334
  }
5032
5335
  return request;
@@ -5049,13 +5352,15 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5049
5352
  @function up.proxy.ajax
5050
5353
  @param {String} request.url
5051
5354
  @param {String} [request.method='GET']
5052
- @param {String} [request.selector='body']
5355
+ @param {String} [request.target='body']
5053
5356
  @param {Boolean} [request.cache]
5054
5357
  Whether to use a cached response, if available.
5055
5358
  If set to `false` a network connection will always be attempted.
5056
5359
  @param {Object} [request.headers={}]
5057
5360
  An object of additional header key/value pairs to send along
5058
5361
  with the request.
5362
+ @param {Object} [request.data={}]
5363
+ An object of request parameters.
5059
5364
  @return
5060
5365
  A promise for the response that is API-compatible with the
5061
5366
  promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
@@ -5065,7 +5370,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5065
5370
  var forceCache, ignoreCache, pending, promise, request;
5066
5371
  forceCache = options.cache === true;
5067
5372
  ignoreCache = options.cache === false;
5068
- request = u.only(options, 'url', 'method', 'data', 'selector', 'headers', '_normalized');
5373
+ request = u.only(options, 'url', 'method', 'data', 'target', 'headers', '_normalized');
5069
5374
  pending = true;
5070
5375
  if (!isIdempotent(request) && !forceCache) {
5071
5376
  clear();
@@ -5085,7 +5390,6 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5085
5390
  }
5086
5391
  return promise;
5087
5392
  };
5088
- SAFE_HTTP_METHODS = ['GET', 'OPTIONS', 'HEAD'];
5089
5393
 
5090
5394
  /**
5091
5395
  Returns `true` if the proxy is not currently waiting
@@ -5190,9 +5494,20 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5190
5494
  };
5191
5495
  load = function(request) {
5192
5496
  var promise;
5193
- u.debug('Loading URL %o', request.url);
5497
+ u.debug('Fetching %o via %o', request.url, request.method);
5194
5498
  up.emit('up:proxy:load', request);
5195
- promise = u.ajax(request);
5499
+ request = u.copy(request);
5500
+ request.headers || (request.headers = {});
5501
+ request.headers['X-Up-Target'] = request.target;
5502
+ request.data = u.requestDataAsArray(request.data);
5503
+ if (u.contains(config.wrapMethods, request.method)) {
5504
+ request.data.push({
5505
+ name: config.wrapMethodParam,
5506
+ value: request.method
5507
+ });
5508
+ request.method = 'POST';
5509
+ }
5510
+ promise = $.ajax(request);
5196
5511
  promise.always(function() {
5197
5512
  up.emit('up:proxy:received', request);
5198
5513
  return pokeQueue();
@@ -5223,7 +5538,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5223
5538
  @event up:proxy:load
5224
5539
  @param event.url
5225
5540
  @param event.method
5226
- @param event.selector
5541
+ @param event.target
5227
5542
  @experimental
5228
5543
  */
5229
5544
 
@@ -5234,12 +5549,12 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
5234
5549
  @event up:proxy:received
5235
5550
  @param event.url
5236
5551
  @param event.method
5237
- @param event.selector
5552
+ @param event.target
5238
5553
  @experimental
5239
5554
  */
5240
5555
  isIdempotent = function(request) {
5241
5556
  normalizeRequest(request);
5242
- return u.contains(SAFE_HTTP_METHODS, request.method);
5557
+ return u.contains(config.safeMethods, request.method);
5243
5558
  };
5244
5559
  checkPreload = function($link) {
5245
5560
  var curriedPreload, delay;
@@ -5407,7 +5722,7 @@ Read on
5407
5722
 
5408
5723
  (function() {
5409
5724
  up.link = (function($) {
5410
- var allowDefault, childClicked, follow, followMethod, followVariantSelectors, isFollowable, makeFollowable, registerFollowVariant, shouldProcessLinkEvent, u, visit;
5725
+ var allowDefault, childClicked, follow, followMethod, followVariantSelectors, isFollowable, makeFollowable, onAction, shouldProcessLinkEvent, u, visit;
5411
5726
  u = up.util;
5412
5727
 
5413
5728
  /**
@@ -5456,8 +5771,13 @@ Read on
5456
5771
  or any element that is marked up with an `up-href` attribute.
5457
5772
  @param {String} [options.target]
5458
5773
  The selector to replace.
5459
- Defaults to the `up-target` attribute on `link`,
5460
- or to `body` if such an attribute does not exist.
5774
+ Defaults to the `up-target` attribute on `link`, or to `body` if such an attribute does not exist.
5775
+ @param {String} [options.failTarget]
5776
+ The selector to replace if the server responds with a non-200 status code.
5777
+ Defaults to the `up-fail-target` attribute on `link`, or to `body` if such an attribute does not exist.
5778
+ @param {String} [options.confirm]
5779
+ A message that will be displayed in a cancelable confirmation dialog
5780
+ before the link is followed.
5461
5781
  @param {Function|String} [options.transition]
5462
5782
  A transition function or name.
5463
5783
  @param {Number} [options.duration]
@@ -5484,20 +5804,25 @@ Read on
5484
5804
  @stable
5485
5805
  */
5486
5806
  follow = function(linkOrSelector, options) {
5487
- var $link, selector, url;
5807
+ var $link, target, url;
5488
5808
  $link = $(linkOrSelector);
5489
5809
  options = u.options(options);
5490
5810
  url = u.option($link.attr('up-href'), $link.attr('href'));
5491
- selector = u.option(options.target, $link.attr('up-target'), 'body');
5492
- options.transition = u.option(options.transition, u.castedAttr($link, 'up-transition'), u.castedAttr($link, 'up-animation'));
5811
+ target = u.option(options.target, $link.attr('up-target'), 'body');
5812
+ options.failTarget = u.option(options.failTarget, $link.attr('up-fail-target'), 'body');
5813
+ options.transition = u.option(options.transition, u.castedAttr($link, 'up-transition'), 'none');
5814
+ options.failTransition = u.option(options.failTransition, u.castedAttr($link, 'up-fail-transition'), 'none');
5493
5815
  options.history = u.option(options.history, u.castedAttr($link, 'up-history'));
5494
5816
  options.reveal = u.option(options.reveal, u.castedAttr($link, 'up-reveal'), true);
5495
5817
  options.cache = u.option(options.cache, u.castedAttr($link, 'up-cache'));
5496
5818
  options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($link, 'up-restore-scroll'));
5497
5819
  options.method = followMethod($link, options);
5498
5820
  options.origin = u.option(options.origin, $link);
5821
+ options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
5499
5822
  options = u.merge(options, up.motion.animateOptions(options, $link));
5500
- return up.replace(selector, url, options);
5823
+ return up.browser.confirm(options.confirm).then(function() {
5824
+ return up.replace(target, url, options);
5825
+ });
5501
5826
  };
5502
5827
 
5503
5828
  /**
@@ -5541,7 +5866,7 @@ Read on
5541
5866
  @internal
5542
5867
  */
5543
5868
  allowDefault = function(event) {};
5544
- registerFollowVariant = function(selector, handler) {
5869
+ onAction = function(selector, handler) {
5545
5870
  followVariantSelectors.push(selector);
5546
5871
  up.on('click', "a" + selector + ", [up-href]" + selector, function(event, $link) {
5547
5872
  if (shouldProcessLinkEvent(event, $link)) {
@@ -5635,9 +5960,14 @@ Read on
5635
5960
  @selector a[up-target]
5636
5961
  @param {String} up-target
5637
5962
  The CSS selector to replace
5963
+ @param [up-fail-target='body']
5964
+ The selector to replace if the server responds with a non-200 status code.
5638
5965
  @param {String} [up-href]
5639
5966
  The destination URL to follow.
5640
5967
  If omitted, the the link's `href` attribute will be used.
5968
+ @param {String} [up-confirm]
5969
+ A message that will be displayed in a cancelable confirmation dialog
5970
+ before the link is followed.
5641
5971
  @param {String} [up-reveal='true']
5642
5972
  Whether to reveal the target element within its viewport before updating.
5643
5973
  @param {String} [up-restore-scroll='false']
@@ -5651,7 +5981,7 @@ Read on
5651
5981
  Set this to `'false'` to prevent the current URL from being updated.
5652
5982
  @stable
5653
5983
  */
5654
- registerFollowVariant('[up-target]', function($link) {
5984
+ onAction('[up-target]', function($link) {
5655
5985
  return follow($link);
5656
5986
  });
5657
5987
 
@@ -5699,9 +6029,14 @@ Read on
5699
6029
  opening the destination in a new tab.
5700
6030
 
5701
6031
  @selector a[up-follow]
6032
+ @param [up-fail-target='body']
6033
+ The selector to replace if the server responds with a non-200 status code.
5702
6034
  @param [up-href]
5703
6035
  The destination URL to follow.
5704
6036
  If omitted, the the link's `href` attribute will be used.
6037
+ @param {String} [up-confirm]
6038
+ A message that will be displayed in a cancelable confirmation dialog
6039
+ before the link is followed.
5705
6040
  @param [up-history]
5706
6041
  Set this to `'false'` to prevent the current URL from being updated.
5707
6042
  @param [up-restore-scroll='false']
@@ -5709,7 +6044,7 @@ Read on
5709
6044
  within the response.
5710
6045
  @stable
5711
6046
  */
5712
- registerFollowVariant('[up-follow]', function($link) {
6047
+ onAction('[up-follow]', function($link) {
5713
6048
  return follow($link);
5714
6049
  });
5715
6050
 
@@ -5732,27 +6067,46 @@ Read on
5732
6067
 
5733
6068
  `up-expand` also expands links that open [modals](/up.modal) or [popups](/up.popup).
5734
6069
 
6070
+ \#\#\#\# Elements with multiple contained links
6071
+
6072
+ If a container contains more than one link, you can set the value of the
6073
+ `up-expand` attribute to a CSS selector to define which link should be expanded:
6074
+
6075
+ <div class="notification" up-expand=".close">
6076
+ Record was saved!
6077
+ <a class="details" href="/records/5">Details</a>
6078
+ <a class="close" href="/records">Close</a>
6079
+ </div>
6080
+
5735
6081
  @selector [up-expand]
6082
+ @param {String} [up-expand]
6083
+ A CSS selector that defines which containing link should be expanded.
6084
+
6085
+ If omitted, the first contained link will be expanded.
5736
6086
  @stable
5737
6087
  */
5738
6088
  up.compiler('[up-expand]', function($area) {
5739
- var attribute, i, len, link, name, newAttrs, ref, upAttributePattern;
5740
- link = $area.find('a, [up-href]').get(0);
5741
- link || u.error('No link to expand within %o', $area);
5742
- upAttributePattern = /^up-/;
5743
- newAttrs = {};
5744
- newAttrs['up-href'] = $(link).attr('href');
5745
- ref = link.attributes;
5746
- for (i = 0, len = ref.length; i < len; i++) {
5747
- attribute = ref[i];
5748
- name = attribute.name;
5749
- if (name.match(upAttributePattern)) {
5750
- newAttrs[name] = attribute.value;
6089
+ var $childLinks, attribute, i, len, link, name, newAttrs, ref, selector, upAttributePattern;
6090
+ $childLinks = $area.find('a, [up-href]');
6091
+ if (selector = $area.attr('up-expand')) {
6092
+ $childLinks = $childLinks.filter(selector);
6093
+ }
6094
+ if (link = $childLinks.get(0)) {
6095
+ upAttributePattern = /^up-/;
6096
+ newAttrs = {};
6097
+ newAttrs['up-href'] = $(link).attr('href');
6098
+ ref = link.attributes;
6099
+ for (i = 0, len = ref.length; i < len; i++) {
6100
+ attribute = ref[i];
6101
+ name = attribute.name;
6102
+ if (name.match(upAttributePattern)) {
6103
+ newAttrs[name] = attribute.value;
6104
+ }
5751
6105
  }
6106
+ u.setMissingAttrs($area, newAttrs);
6107
+ $area.removeAttr('up-expand');
6108
+ return makeFollowable($area);
5752
6109
  }
5753
- u.setMissingAttrs($area, newAttrs);
5754
- $area.removeAttr('up-expand');
5755
- return makeFollowable($area);
5756
6110
  });
5757
6111
 
5758
6112
  /**
@@ -5797,7 +6151,7 @@ Read on
5797
6151
  shouldProcessLinkEvent: shouldProcessLinkEvent,
5798
6152
  childClicked: childClicked,
5799
6153
  followMethod: followMethod,
5800
- registerFollowVariant: registerFollowVariant
6154
+ onAction: onAction
5801
6155
  };
5802
6156
  })(jQuery);
5803
6157
 
@@ -5822,7 +6176,7 @@ open dialogs with sub-forms, etc. all without losing form state.
5822
6176
  var slice = [].slice;
5823
6177
 
5824
6178
  up.form = (function($) {
5825
- var autosubmit, config, observe, observeForm, reset, resolveValidateTarget, submit, u, validate;
6179
+ var autosubmit, config, currentValuesForToggle, observe, observeForm, reset, resolveValidateTarget, submit, toggleTargets, u, validate;
5826
6180
  u = up.util;
5827
6181
 
5828
6182
  /**
@@ -5924,77 +6278,41 @@ open dialogs with sub-forms, etc. all without losing form state.
5924
6278
  @stable
5925
6279
  */
5926
6280
  submit = function(formOrSelector, options) {
5927
- var $form, failureSelector, failureTransition, hasFileInputs, headers, historyOption, httpMethod, implantOptions, request, successSelector, successTransition, successUrl, url, useCache;
6281
+ var $form, hasFileInputs, promise, target, url;
5928
6282
  $form = $(formOrSelector).closest('form');
5929
6283
  options = u.options(options);
5930
- successSelector = u.option(options.target, $form.attr('up-target'), 'body');
5931
- successSelector = up.flow.resolveSelector(successSelector, options);
5932
- failureSelector = u.option(options.failTarget, $form.attr('up-fail-target')) || u.selectorForElement($form);
5933
- failureSelector = up.flow.resolveSelector(failureSelector, options);
5934
- historyOption = u.option(options.history, u.castedAttr($form, 'up-history'), true);
5935
- successTransition = u.option(options.transition, u.castedAttr($form, 'up-transition'));
5936
- failureTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), successTransition);
5937
- httpMethod = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase();
5938
- headers = u.option(options.headers, {});
5939
- implantOptions = {};
5940
- implantOptions.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true);
5941
- implantOptions.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'));
5942
- implantOptions.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'));
5943
- implantOptions.origin = u.option(options.origin, $form);
5944
- implantOptions = u.extend(implantOptions, up.motion.animateOptions(options, $form));
5945
- useCache = u.option(options.cache, u.castedAttr($form, 'up-cache'));
6284
+ target = u.option(options.target, $form.attr('up-target'), 'body');
5946
6285
  url = u.option(options.url, $form.attr('action'), up.browser.url());
6286
+ options.failTarget = u.option(options.failTarget, $form.attr('up-fail-target')) || u.selectorForElement($form);
6287
+ options.history = u.option(options.history, u.castedAttr($form, 'up-history'), true);
6288
+ options.transition = u.option(options.transition, u.castedAttr($form, 'up-transition'), 'none');
6289
+ options.failTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), 'none');
6290
+ options.method = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase();
6291
+ options.headers = u.option(options.headers, {});
6292
+ options.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true);
6293
+ options.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'));
6294
+ options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'));
6295
+ options.origin = u.option(options.origin, $form);
6296
+ options.data = $form.serializeArray();
6297
+ options = u.merge(options, up.motion.animateOptions(options, $form));
5947
6298
  hasFileInputs = $form.find('input[type=file]').length;
5948
6299
  if (options.validate) {
5949
- headers['X-Up-Validate'] = options.validate;
6300
+ options.headers || (options.headers = {});
6301
+ options.headers['X-Up-Validate'] = options.validate;
5950
6302
  if (hasFileInputs) {
5951
6303
  return u.unresolvablePromise();
5952
6304
  }
5953
6305
  }
5954
6306
  $form.addClass('up-active');
5955
- if (hasFileInputs || (!up.browser.canPushState() && historyOption !== false)) {
6307
+ if (hasFileInputs || (!up.browser.canPushState() && options.history !== false)) {
5956
6308
  $form.get(0).submit();
5957
6309
  return u.unresolvablePromise();
5958
6310
  }
5959
- request = {
5960
- url: url,
5961
- method: httpMethod,
5962
- data: $form.serialize(),
5963
- selector: successSelector,
5964
- cache: useCache,
5965
- headers: headers
5966
- };
5967
- successUrl = function(xhr) {
5968
- var currentLocation;
5969
- url = void 0;
5970
- if (u.isGiven(historyOption)) {
5971
- if (historyOption === false || u.isString(historyOption)) {
5972
- url = historyOption;
5973
- } else if (currentLocation = u.locationFromXhr(xhr)) {
5974
- url = currentLocation;
5975
- } else if (request.type === 'GET') {
5976
- url = request.url + '?' + request.data;
5977
- }
5978
- }
5979
- return u.option(url, false);
5980
- };
5981
- return up.proxy.ajax(request).always(function() {
6311
+ promise = up.replace(target, url, options);
6312
+ promise.always(function() {
5982
6313
  return $form.removeClass('up-active');
5983
- }).done(function(html, textStatus, xhr) {
5984
- var successOptions;
5985
- successOptions = u.merge(implantOptions, {
5986
- history: successUrl(xhr),
5987
- transition: successTransition
5988
- });
5989
- return up.flow.implant(successSelector, html, successOptions);
5990
- }).fail(function(xhr, textStatus, errorThrown) {
5991
- var failureOptions, html;
5992
- html = xhr.responseText;
5993
- failureOptions = u.merge(implantOptions, {
5994
- transition: failureTransition
5995
- });
5996
- return up.flow.implant(failureSelector, html, failureOptions);
5997
6314
  });
6315
+ return promise;
5998
6316
  };
5999
6317
 
6000
6318
  /**
@@ -6172,7 +6490,6 @@ open dialogs with sub-forms, etc. all without losing form state.
6172
6490
  @stable
6173
6491
  */
6174
6492
  autosubmit = function(selectorOrElement, options) {
6175
- console.log("autosubmit %o", selectorOrElement);
6176
6493
  return observe(selectorOrElement, options, function(value, $field) {
6177
6494
  var $form;
6178
6495
  $form = $field.closest('form');
@@ -6188,7 +6505,7 @@ open dialogs with sub-forms, etc. all without losing form state.
6188
6505
  if (u.isBlank(target)) {
6189
6506
  target || (target = u.detect(config.validateTargets, function(defaultTarget) {
6190
6507
  var resolvedDefault;
6191
- resolvedDefault = up.flow.resolveSelector(defaultTarget, options);
6508
+ resolvedDefault = up.flow.resolveSelector(defaultTarget, options.origin);
6192
6509
  return $field.closest(resolvedDefault).length;
6193
6510
  }));
6194
6511
  }
@@ -6240,6 +6557,109 @@ open dialogs with sub-forms, etc. all without losing form state.
6240
6557
  promise = up.submit($form, options);
6241
6558
  return promise;
6242
6559
  };
6560
+ currentValuesForToggle = function($field) {
6561
+ var $checkedButton, value, values;
6562
+ values = void 0;
6563
+ if ($field.is('input[type=checkbox]')) {
6564
+ if ($field.is(':checked')) {
6565
+ values = [':checked', ':present', $field.val()];
6566
+ } else {
6567
+ values = [':unchecked', ':blank'];
6568
+ }
6569
+ } else if ($field.is('input[type=radio]')) {
6570
+ console.log('-- it is a radio button --');
6571
+ $checkedButton = $field.closest('form, body').find("input[type='radio'][name='" + ($field.attr('name')) + "']:checked");
6572
+ console.log('checked button is %o', $checkedButton);
6573
+ console.log('checked button val is %o', $checkedButton.val());
6574
+ if ($checkedButton.length) {
6575
+ values = [':checked', ':present', $checkedButton.val()];
6576
+ } else {
6577
+ values = [':unchecked', ':blank'];
6578
+ }
6579
+ } else {
6580
+ console.log('-- else -- for %o', $field);
6581
+ value = $field.val();
6582
+ if (u.isPresent(value)) {
6583
+ values = [':present', value];
6584
+ } else {
6585
+ values = [':blank'];
6586
+ }
6587
+ }
6588
+ return values;
6589
+ };
6590
+ currentValuesForToggle = function($field) {
6591
+ var $checkedButton, meta, value, values;
6592
+ if ($field.is('input[type=checkbox]')) {
6593
+ if ($field.is(':checked')) {
6594
+ value = $field.val();
6595
+ meta = ':checked';
6596
+ } else {
6597
+ meta = ':unchecked';
6598
+ }
6599
+ } else if ($field.is('input[type=radio]')) {
6600
+ $checkedButton = $field.closest('form, body').find("input[type='radio'][name='" + ($field.attr('name')) + "']:checked");
6601
+ if ($checkedButton.length) {
6602
+ meta = ':checked';
6603
+ value = $checkedButton.val();
6604
+ } else {
6605
+ meta = ':unchecked';
6606
+ }
6607
+ } else {
6608
+ value = $field.val();
6609
+ }
6610
+ values = [];
6611
+ if (u.isPresent(value)) {
6612
+ values.push(value);
6613
+ values.push(':present');
6614
+ } else {
6615
+ values.push(':blank');
6616
+ }
6617
+ if (u.isPresent(meta)) {
6618
+ values.push(meta);
6619
+ }
6620
+ return values;
6621
+ };
6622
+
6623
+ /**
6624
+ Shows or hides a target selector depending on the value.
6625
+
6626
+ See [`[up-toggle]`](/up-toggle) for more documentation and examples.
6627
+
6628
+ This function does not currently have a very useful API outside
6629
+ of our use for `up-toggle`'s UJS behavior, that's why it's currently
6630
+ still marked `@internal`.
6631
+
6632
+ @function up.form.toggle
6633
+ @param {String|Element|jQuery} fieldOrSelector
6634
+ @param {String} [options.target]
6635
+ The target selectors to toggle.
6636
+ Defaults to an `up-toggle` attribute on the given field.
6637
+ @internal
6638
+ */
6639
+ toggleTargets = function(fieldOrSelector, options) {
6640
+ var $field, fieldValues, targets;
6641
+ $field = $(fieldOrSelector);
6642
+ options = u.options(options);
6643
+ targets = u.option(options.target, $field.attr('up-toggle'));
6644
+ u.isPresent(targets) || u.error("No toggle target given for %o", $field);
6645
+ fieldValues = currentValuesForToggle($field);
6646
+ return $(targets).each(function() {
6647
+ var $target, hideValues, show, showValues;
6648
+ $target = $(this);
6649
+ if (hideValues = $target.attr('up-hide-for')) {
6650
+ hideValues = hideValues.split(' ');
6651
+ show = u.intersect(fieldValues, hideValues).length === 0;
6652
+ } else {
6653
+ if (showValues = $target.attr('up-show-for')) {
6654
+ showValues = showValues.split(' ');
6655
+ } else {
6656
+ showValues = [':present', ':checked'];
6657
+ }
6658
+ show = u.intersect(fieldValues, showValues).length > 0;
6659
+ }
6660
+ return $target.toggle(show);
6661
+ });
6662
+ };
6243
6663
 
6244
6664
  /**
6245
6665
  Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
@@ -6294,7 +6714,7 @@ open dialogs with sub-forms, etc. all without losing form state.
6294
6714
 
6295
6715
  When the form's action performs a redirect, the server should echo
6296
6716
  the new request's URL as a response header `X-Up-Location`
6297
- and the request's HTTP method as `X-Up-Method`.
6717
+ and the request's HTTP method as `X-Up-Method: GET`.
6298
6718
 
6299
6719
  If you are using Up.js via the `upjs-rails` gem, these headers
6300
6720
  are set automatically for every request.
@@ -6490,6 +6910,83 @@ open dialogs with sub-forms, etc. all without losing form state.
6490
6910
  return validate($field);
6491
6911
  });
6492
6912
 
6913
+ /**
6914
+ Show or hide part of a form if certain options are selected or boxes are checked.
6915
+
6916
+ \#\#\#\# Example
6917
+
6918
+ The triggering input gets an `up-toggle` attribute with a selector for the elements to show or hide:
6919
+
6920
+ <select name="advancedness" up-toggle=".target">
6921
+ <option value="basic">Basic parts</option>
6922
+ <option value="advanced">Advanced parts</option>
6923
+ <option value="very-advanced">Very advanced parts</option>
6924
+ </select>
6925
+
6926
+ The target elements get a space-separated list of select values for which they are shown or hidden:
6927
+
6928
+ <div class="target" up-show-for="basic">
6929
+ only shown for advancedness = basic
6930
+ </div>
6931
+
6932
+ <div class="target" up-hide-for="basic">
6933
+ hidden for advancedness = basic
6934
+ </div>
6935
+
6936
+ <div class="target" up-show-for="advanced very-advanced">
6937
+ shown for advancedness = advanced or very-advanced
6938
+ </div>
6939
+
6940
+ For checkboxes you can also use the pseudo-values `:checked` or `:unchecked` like so:
6941
+
6942
+ <input type="checkbox" name="flag" up-toggle=".target">
6943
+
6944
+ <div class="target" up-show-for=":checked">
6945
+ only shown when checkbox is checked
6946
+ </div>
6947
+
6948
+ You can also use the pseudo-values `:blank` to match an empty input value,
6949
+ or `:present` to match a non-empty input value:
6950
+
6951
+ <input type="text" name="email" up-toggle=".target">
6952
+
6953
+ <div class="target" up-show-for=":blank">
6954
+ please enter an email address
6955
+ </div>
6956
+
6957
+ @selector [up-toggle]
6958
+ @stable
6959
+ */
6960
+
6961
+ /**
6962
+ Show this element only if a form field has a given value.
6963
+
6964
+ See [`[up-toggle]`](/up-toggle) for more documentation and examples.
6965
+
6966
+ @selector [up-show-for]
6967
+ @param up-show-for
6968
+ A space-separated list of values for which to show this element.
6969
+ @stable
6970
+ */
6971
+
6972
+ /**
6973
+ Hide this element if a form field has a given value.
6974
+
6975
+ See [`[up-toggle]`](/up-toggle) for more documentation and examples.
6976
+
6977
+ @selector [up-hide-for]
6978
+ @param up-hide-for
6979
+ A space-separated list of values for which to show this element.
6980
+ @stable
6981
+ */
6982
+ up.on('change', '[up-toggle]', function(event, $field) {
6983
+ console.log("CHANGE EVENT");
6984
+ return toggleTargets($field);
6985
+ });
6986
+ up.compiler('[up-toggle]', function($field) {
6987
+ return toggleTargets($field);
6988
+ });
6989
+
6493
6990
  /**
6494
6991
  Observes this field or form and runs a callback when a value changes.
6495
6992
 
@@ -6558,7 +7055,8 @@ open dialogs with sub-forms, etc. all without losing form state.
6558
7055
  config: config,
6559
7056
  submit: submit,
6560
7057
  observe: observe,
6561
- validate: validate
7058
+ validate: validate,
7059
+ toggleTargets: toggleTargets
6562
7060
  };
6563
7061
  })(jQuery);
6564
7062
 
@@ -6619,7 +7117,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6619
7117
 
6620
7118
  (function() {
6621
7119
  up.popup = (function($) {
6622
- var attach, autoclose, close, config, contains, coveredUrl, createHiddenPopup, currentUrl, discardHistory, ensureInViewport, rememberHistory, reset, setPosition, u, updated;
7120
+ var attach, autoclose, close, config, contains, coveredUrl, createFrame, currentUrl, discardHistory, ensureInViewport, isOpen, reset, setPosition, u;
6623
7121
  u = up.util;
6624
7122
 
6625
7123
  /**
@@ -6641,9 +7139,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6641
7139
  @experimental
6642
7140
  */
6643
7141
  coveredUrl = function() {
6644
- var $popup;
6645
- $popup = $('.up-popup');
6646
- return $popup.attr('up-covered-url');
7142
+ return $('.up-popup').attr('up-covered-url');
6647
7143
  };
6648
7144
 
6649
7145
  /**
@@ -6669,11 +7165,13 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6669
7165
  history: false
6670
7166
  });
6671
7167
  reset = function() {
6672
- close();
7168
+ close({
7169
+ animation: false
7170
+ });
6673
7171
  return config.reset();
6674
7172
  };
6675
- setPosition = function($link, $popup, position) {
6676
- var css, linkBox;
7173
+ setPosition = function($link, position) {
7174
+ var $popup, css, linkBox;
6677
7175
  linkBox = u.measure($link, {
6678
7176
  full: true
6679
7177
  });
@@ -6703,6 +7201,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6703
7201
  return u.error("Unknown position %o", position);
6704
7202
  }
6705
7203
  })();
7204
+ $popup = $('.up-popup');
6706
7205
  $popup.attr('up-position', position);
6707
7206
  $popup.css(css);
6708
7207
  return ensureInViewport($popup);
@@ -6741,42 +7240,34 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6741
7240
  }
6742
7241
  }
6743
7242
  };
6744
- rememberHistory = function() {
6745
- var $popup;
6746
- $popup = $('.up-popup');
6747
- $popup.attr('up-covered-url', up.browser.url());
6748
- return $popup.attr('up-covered-title', document.title);
6749
- };
6750
7243
  discardHistory = function() {
6751
7244
  var $popup;
6752
7245
  $popup = $('.up-popup');
6753
7246
  $popup.removeAttr('up-covered-url');
6754
7247
  return $popup.removeAttr('up-covered-title');
6755
7248
  };
6756
- createHiddenPopup = function($link, selector, sticky) {
7249
+ createFrame = function(target, options) {
6757
7250
  var $placeholder, $popup;
6758
7251
  $popup = u.$createElementFromSelector('.up-popup');
6759
- if (sticky) {
7252
+ if (options.sticky) {
6760
7253
  $popup.attr('up-sticky', '');
6761
7254
  }
6762
- $placeholder = u.$createElementFromSelector(selector);
7255
+ $popup.attr('up-covered-url', up.browser.url());
7256
+ $popup.attr('up-covered-title', document.title);
7257
+ $placeholder = u.$createElementFromSelector(target);
6763
7258
  $placeholder.appendTo($popup);
6764
7259
  $popup.appendTo(document.body);
6765
- rememberHistory();
6766
- $popup.hide();
6767
7260
  return $popup;
6768
7261
  };
6769
- updated = function($link, position, animation, animateOptions) {
6770
- var $popup, deferred;
6771
- $popup = $('.up-popup');
6772
- if ($popup.is(':hidden')) {
6773
- $popup.show();
6774
- setPosition($link, $popup, position);
6775
- deferred = up.animate($popup, animation, animateOptions);
6776
- return deferred.then(function() {
6777
- return up.emit('up:popup:opened');
6778
- });
6779
- }
7262
+
7263
+ /**
7264
+ Returns whether popup modal is currently open.
7265
+
7266
+ @function up.popup.isOpen
7267
+ @stable
7268
+ */
7269
+ isOpen = function() {
7270
+ return $('.up-popup').length > 0;
6780
7271
  };
6781
7272
 
6782
7273
  /**
@@ -6791,6 +7282,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6791
7282
  Defines where the popup is attached to the opening element.
6792
7283
 
6793
7284
  Valid values are `bottom-right`, `bottom-left`, `top-right` and `top-left`.
7285
+ @param {String} [options.confirm]
7286
+ A message that will be displayed in a cancelable confirmation dialog
7287
+ before the modal is being opened.
6794
7288
  @param {String} [options.animation]
6795
7289
  The animation to use when opening the popup.
6796
7290
  @param {Number} [options.duration]
@@ -6804,37 +7298,56 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6804
7298
  open even if the page changes in the background.
6805
7299
  @param {Object} [options.history=false]
6806
7300
  @return {Promise}
6807
- A promise that will be resolved when the popup has been loaded and rendered.
7301
+ A promise that will be resolved when the popup has been loaded and
7302
+ the opening animation has completed.
6808
7303
  @stable
6809
7304
  */
6810
7305
  attach = function(linkOrSelector, options) {
6811
- var $link, animateOptions, animation, history, position, promise, selector, sticky, url;
7306
+ var $link, animateOptions, target, url;
6812
7307
  $link = $(linkOrSelector);
6813
7308
  $link.length || u.error('Cannot attach popup to non-existing element %o', linkOrSelector);
6814
7309
  options = u.options(options);
6815
7310
  url = u.option(options.url, $link.attr('href'));
6816
- selector = u.option(options.target, $link.attr('up-popup'), 'body');
6817
- position = u.option(options.position, $link.attr('up-position'), config.position);
6818
- animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
6819
- sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
6820
- history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
7311
+ target = u.option(options.target, $link.attr('up-popup'), 'body');
7312
+ options.position = u.option(options.position, $link.attr('up-position'), config.position);
7313
+ options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
7314
+ options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
7315
+ options.history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
7316
+ options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
6821
7317
  animateOptions = up.motion.animateOptions(options, $link);
6822
- close();
6823
- if (up.bus.nobodyPrevents('up:popup:open', {
6824
- url: url
6825
- })) {
6826
- createHiddenPopup($link, selector, sticky);
6827
- promise = up.replace(selector, url, {
6828
- history: history,
6829
- requireMatch: false
6830
- });
6831
- promise.then(function() {
6832
- return updated($link, position, animation, animateOptions);
6833
- });
6834
- return promise;
6835
- } else {
6836
- return u.unresolvableDeferred();
6837
- }
7318
+ return up.browser.confirm(options.confirm).then(function() {
7319
+ var promise, wasOpen;
7320
+ if (up.bus.nobodyPrevents('up:popup:open', {
7321
+ url: url
7322
+ })) {
7323
+ wasOpen = isOpen();
7324
+ if (wasOpen) {
7325
+ close({
7326
+ animation: false
7327
+ });
7328
+ }
7329
+ options.beforeSwap = function() {
7330
+ return createFrame(target, options);
7331
+ };
7332
+ promise = up.replace(target, url, u.merge(options, {
7333
+ animation: false
7334
+ }));
7335
+ promise = promise.then(function() {
7336
+ return setPosition($link, options.position);
7337
+ });
7338
+ if (!wasOpen) {
7339
+ promise = promise.then(function() {
7340
+ return up.animate($('.up-popup'), options.animation, animateOptions);
7341
+ });
7342
+ }
7343
+ promise = promise.then(function() {
7344
+ return up.emit('up:popup:opened');
7345
+ });
7346
+ return promise;
7347
+ } else {
7348
+ return u.unresolvableDeferred();
7349
+ }
7350
+ });
6838
7351
  };
6839
7352
 
6840
7353
  /**
@@ -6948,12 +7461,15 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
6948
7461
  Defines where the popup is attached to the opening element.
6949
7462
 
6950
7463
  Valid values are `bottom-right`, `bottom-left`, `top-right` and `top-left`.
7464
+ @param {String} [up-confirm]
7465
+ A message that will be displayed in a cancelable confirmation dialog
7466
+ before the popup is opened.
6951
7467
  @param [up-sticky]
6952
7468
  If set to `true`, the popup remains
6953
7469
  open even if the page changes in the background.
6954
7470
  @stable
6955
7471
  */
6956
- up.link.registerFollowVariant('[up-popup]', function($link) {
7472
+ up.link.onAction('[up-popup]', function($link) {
6957
7473
  if ($link.is('.up-current')) {
6958
7474
  return close();
6959
7475
  } else {
@@ -7020,7 +7536,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7020
7536
  },
7021
7537
  source: function() {
7022
7538
  return up.error('up.popup.source no longer exists. Please use up.popup.url instead.');
7023
- }
7539
+ },
7540
+ isOpen: isOpen
7024
7541
  };
7025
7542
  })(jQuery);
7026
7543
 
@@ -7086,7 +7603,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7086
7603
 
7087
7604
  (function() {
7088
7605
  up.modal = (function($) {
7089
- var autoclose, close, config, contains, coveredUrl, createHiddenModal, currentUrl, discardHistory, follow, open, rememberHistory, reset, shiftElements, templateHtml, u, unshiftElements, updated, visit;
7606
+ var autoclose, close, config, contains, coveredUrl, createFrame, currentUrl, discardHistory, follow, isOpen, open, reset, shiftElements, templateHtml, u, unshiftElements, unshifters, visit;
7090
7607
  u = up.util;
7091
7608
 
7092
7609
  /**
@@ -7161,12 +7678,12 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7161
7678
  @experimental
7162
7679
  */
7163
7680
  coveredUrl = function() {
7164
- var $modal;
7165
- $modal = $('.up-modal');
7166
- return $modal.attr('up-covered-url');
7681
+ return $('.up-modal').attr('up-covered-url');
7167
7682
  };
7168
7683
  reset = function() {
7169
- close();
7684
+ close({
7685
+ animation: false
7686
+ });
7170
7687
  currentUrl = void 0;
7171
7688
  return config.reset();
7172
7689
  };
@@ -7179,20 +7696,15 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7179
7696
  return template;
7180
7697
  }
7181
7698
  };
7182
- rememberHistory = function() {
7183
- var $modal;
7184
- $modal = $('.up-modal');
7185
- $modal.attr('up-covered-url', up.browser.url());
7186
- return $modal.attr('up-covered-title', document.title);
7187
- };
7188
7699
  discardHistory = function() {
7189
7700
  var $modal;
7190
7701
  $modal = $('.up-modal');
7191
7702
  $modal.removeAttr('up-covered-url');
7192
7703
  return $modal.removeAttr('up-covered-title');
7193
7704
  };
7194
- createHiddenModal = function(options) {
7705
+ createFrame = function(target, options) {
7195
7706
  var $content, $dialog, $modal, $placeholder;
7707
+ shiftElements();
7196
7708
  $modal = $(templateHtml());
7197
7709
  if (options.sticky) {
7198
7710
  $modal.attr('up-sticky', '');
@@ -7210,14 +7722,12 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7210
7722
  $dialog.css('height', options.height);
7211
7723
  }
7212
7724
  $content = $modal.find('.up-modal-content');
7213
- $placeholder = u.$createElementFromSelector(options.selector);
7725
+ $placeholder = u.$createElementFromSelector(target);
7214
7726
  $placeholder.appendTo($content);
7215
7727
  $modal.appendTo(document.body);
7216
- rememberHistory();
7217
- $modal.hide();
7218
7728
  return $modal;
7219
7729
  };
7220
- unshiftElements = [];
7730
+ unshifters = [];
7221
7731
  shiftElements = function() {
7222
7732
  var bodyRightPadding, bodyRightShift, scrollbarWidth, unshiftBody;
7223
7733
  scrollbarWidth = u.scrollbarWidth();
@@ -7227,29 +7737,35 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7227
7737
  'padding-right': bodyRightShift + "px",
7228
7738
  'overflow-y': 'hidden'
7229
7739
  });
7230
- unshiftElements.push(unshiftBody);
7740
+ unshifters.push(unshiftBody);
7231
7741
  return up.layout.anchoredRight().each(function() {
7232
- var $element, elementRight, elementRightShift, unshiftElement;
7742
+ var $element, elementRight, elementRightShift, unshifter;
7233
7743
  $element = $(this);
7234
7744
  elementRight = parseInt($element.css('right'));
7235
7745
  elementRightShift = scrollbarWidth + elementRight;
7236
- unshiftElement = u.temporaryCss($element, {
7746
+ unshifter = u.temporaryCss($element, {
7237
7747
  'right': elementRightShift
7238
7748
  });
7239
- return unshiftElements.push(unshiftElement);
7749
+ return unshifters.push(unshifter);
7240
7750
  });
7241
7751
  };
7242
- updated = function(animation, animateOptions) {
7243
- var $modal, deferred;
7244
- $modal = $('.up-modal');
7245
- if ($modal.is(':hidden')) {
7246
- shiftElements();
7247
- $modal.show();
7248
- deferred = up.animate($modal, animation, animateOptions);
7249
- return deferred.then(function() {
7250
- return up.emit('up:modal:opened');
7251
- });
7752
+ unshiftElements = function() {
7753
+ var results, unshifter;
7754
+ results = [];
7755
+ while (unshifter = unshifters.pop()) {
7756
+ results.push(unshifter());
7252
7757
  }
7758
+ return results;
7759
+ };
7760
+
7761
+ /**
7762
+ Returns whether a modal is currently open.
7763
+
7764
+ @function up.modal.isOpen
7765
+ @stable
7766
+ */
7767
+ isOpen = function() {
7768
+ return $('.up-modal').length > 0;
7253
7769
  };
7254
7770
 
7255
7771
  /**
@@ -7276,6 +7792,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7276
7792
  @param {Boolean} [options.sticky=false]
7277
7793
  If set to `true`, the modal remains
7278
7794
  open even if the page changes in the background.
7795
+ @param {String} [options.confirm]
7796
+ A message that will be displayed in a cancelable confirmation dialog
7797
+ before the modal is being opened.
7279
7798
  @param {Object} [options.history=true]
7280
7799
  Whether to add a browser history entry for the modal's source URL.
7281
7800
  @param {String} [options.animation]
@@ -7287,7 +7806,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7287
7806
  @param {String} [options.easing]
7288
7807
  The timing function that controls the animation's acceleration. [`up.animate`](/up.animate).
7289
7808
  @return {Promise}
7290
- A promise that will be resolved when the popup has been loaded and rendered.
7809
+ A promise that will be resolved when the modal has been loaded and
7810
+ the opening animation has completed.
7291
7811
  @stable
7292
7812
  */
7293
7813
  follow = function(linkOrSelector, options) {
@@ -7317,7 +7837,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7317
7837
  @param {Object} options
7318
7838
  See options for [`up.modal.follow`](/up.modal.follow).
7319
7839
  @return {Promise}
7320
- A promise that will be resolved when the popup has been loaded and rendered.
7840
+ A promise that will be resolved when the modal has been loaded and the opening
7841
+ animation has completed.
7321
7842
  @stable
7322
7843
  */
7323
7844
  visit = function(url, options) {
@@ -7331,40 +7852,49 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7331
7852
  @internal
7332
7853
  */
7333
7854
  open = function(options) {
7334
- var $link, animateOptions, animation, height, history, maxWidth, promise, selector, sticky, url, width;
7855
+ var $link, animateOptions, target, url;
7335
7856
  options = u.options(options);
7336
7857
  $link = u.option(options.$link, u.nullJQuery());
7337
7858
  url = u.option(options.url, $link.attr('up-href'), $link.attr('href'));
7338
- selector = u.option(options.target, $link.attr('up-modal'), 'body');
7339
- width = u.option(options.width, $link.attr('up-width'), config.width);
7340
- maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth);
7341
- height = u.option(options.height, $link.attr('up-height'), config.height);
7342
- animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
7343
- sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
7344
- history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
7859
+ target = u.option(options.target, $link.attr('up-modal'), 'body');
7860
+ options.width = u.option(options.width, $link.attr('up-width'), config.width);
7861
+ options.maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth);
7862
+ options.height = u.option(options.height, $link.attr('up-height'), config.height);
7863
+ options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
7864
+ options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
7865
+ options.history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
7866
+ options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
7345
7867
  animateOptions = up.motion.animateOptions(options, $link);
7346
- close();
7347
- if (up.bus.nobodyPrevents('up:modal:open', {
7348
- url: url
7349
- })) {
7350
- createHiddenModal({
7351
- selector: selector,
7352
- width: width,
7353
- maxWidth: maxWidth,
7354
- height: height,
7355
- sticky: sticky
7356
- });
7357
- promise = up.replace(selector, url, {
7358
- history: history,
7359
- requireMatch: false
7360
- });
7361
- promise.then(function() {
7362
- return updated(animation, animateOptions);
7363
- });
7364
- return promise;
7365
- } else {
7366
- return u.unresolvableDeferred();
7367
- }
7868
+ return up.browser.confirm(options.confirm).then(function() {
7869
+ var promise, wasOpen;
7870
+ if (up.bus.nobodyPrevents('up:modal:open', {
7871
+ url: url
7872
+ })) {
7873
+ wasOpen = isOpen();
7874
+ if (wasOpen) {
7875
+ close({
7876
+ animation: false
7877
+ });
7878
+ }
7879
+ options.beforeSwap = function() {
7880
+ return createFrame(target, options);
7881
+ };
7882
+ promise = up.replace(target, url, u.merge(options, {
7883
+ animation: false
7884
+ }));
7885
+ if (!wasOpen) {
7886
+ promise = promise.then(function() {
7887
+ return up.animate($('.up-modal'), options.animation, animateOptions);
7888
+ });
7889
+ }
7890
+ promise = promise.then(function() {
7891
+ return up.emit('up:modal:opened');
7892
+ });
7893
+ return promise;
7894
+ } else {
7895
+ return u.unresolvablePromise();
7896
+ }
7897
+ });
7368
7898
  };
7369
7899
 
7370
7900
  /**
@@ -7399,7 +7929,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7399
7929
  @stable
7400
7930
  */
7401
7931
  close = function(options) {
7402
- var $modal, deferred;
7932
+ var $modal, promise;
7403
7933
  $modal = $('.up-modal');
7404
7934
  if ($modal.length) {
7405
7935
  if (up.bus.nobodyPrevents('up:modal:close', {
@@ -7411,15 +7941,12 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7411
7941
  title: $modal.attr('up-covered-title')
7412
7942
  });
7413
7943
  currentUrl = void 0;
7414
- deferred = up.destroy($modal, options);
7415
- deferred.then(function() {
7416
- var unshifter;
7417
- while (unshifter = unshiftElements.pop()) {
7418
- unshifter();
7419
- }
7944
+ promise = up.destroy($modal, options);
7945
+ promise = promise.then(function() {
7946
+ unshiftElements();
7420
7947
  return up.emit('up:modal:closed');
7421
7948
  });
7422
- return deferred;
7949
+ return promise;
7423
7950
  } else {
7424
7951
  return u.unresolvableDeferred();
7425
7952
  }
@@ -7480,20 +8007,26 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7480
8007
  a modal dialog.
7481
8008
 
7482
8009
  @selector a[up-modal]
7483
- @param [up-sticky]
7484
- @param [up-animation]
7485
- @param [up-height]
8010
+ @param {String} [up-confirm]
8011
+ A message that will be displayed in a cancelable confirmation dialog
8012
+ before the modal is opened.
8013
+ @param {String} [up-sticky]
8014
+ If set to `"true"`, the modal remains
8015
+ open even if the page changes in the background.
8016
+ @param {String} [up-animation]
8017
+ The animation to use when opening the modal.
8018
+ @param {String} [up-height]
8019
+ The width of the dialog in pixels.
8020
+ By [default](/up.modal.config) the dialog will grow to fit its contents.
7486
8021
  @param [up-width]
7487
- @param [up-history]
8022
+ The width of the dialog in pixels.
8023
+ By [default](/up.modal.config) the dialog will grow to fit its contents.
8024
+ @param [up-history="true"]
8025
+ Whether to add a browser history entry for the modal's source URL.
7488
8026
  @stable
7489
8027
  */
7490
- up.link.registerFollowVariant('[up-modal]', function($link) {
7491
- event.preventDefault();
7492
- if ($link.is('.up-current')) {
7493
- return close();
7494
- } else {
7495
- return follow($link);
7496
- }
8028
+ up.link.onAction('[up-modal]', function($link) {
8029
+ return follow($link);
7497
8030
  });
7498
8031
  up.on('click', 'body', function(event, $body) {
7499
8032
  var $target;
@@ -7554,8 +8087,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
7554
8087
  },
7555
8088
  contains: contains,
7556
8089
  source: function() {
7557
- return up.error('up.popup.source no longer exists. Please use up.popup.url instead.');
7558
- }
8090
+ return up.error('up.modal.source no longer exists. Please use up.popup.url instead.');
8091
+ },
8092
+ isOpen: isOpen
7559
8093
  };
7560
8094
  })(jQuery);
7561
8095
 
@@ -8011,19 +8545,40 @@ Play nice with Rails UJS
8011
8545
 
8012
8546
  (function() {
8013
8547
  up.rails = (function($) {
8014
- var u, willHandle;
8548
+ var csrfField, isRails, u, willHandle;
8015
8549
  u = up.util;
8016
8550
  willHandle = function($element) {
8017
8551
  return $element.is('[up-follow], [up-target], [up-modal], [up-popup]');
8018
8552
  };
8019
- return up.compiler('[data-method]', function($element) {
8020
- if ($.rails && willHandle($element)) {
8021
- u.setMissingAttrs($element, {
8022
- 'up-method': $element.attr('data-method')
8023
- });
8024
- return $element.removeAttr('data-method');
8025
- }
8553
+ isRails = function() {
8554
+ return u.isGiven($.rails);
8555
+ };
8556
+ u.each(['method', 'confirm'], function(feature) {
8557
+ var dataAttribute, upAttribute;
8558
+ dataAttribute = "data-" + feature;
8559
+ upAttribute = "up-" + feature;
8560
+ return up.compiler("[" + dataAttribute + "]", function($element) {
8561
+ var replacement;
8562
+ if (isRails() && willHandle($element)) {
8563
+ replacement = {};
8564
+ replacement[upAttribute] = $element.attr(dataAttribute);
8565
+ u.setMissingAttrs($element, replacement);
8566
+ return $element.removeAttr(dataAttribute);
8567
+ }
8568
+ });
8026
8569
  });
8570
+ csrfField = function() {
8571
+ if (isRails()) {
8572
+ return {
8573
+ name: $.rails.csrfParam(),
8574
+ value: $.rails.csrfToken()
8575
+ };
8576
+ }
8577
+ };
8578
+ return {
8579
+ csrfField: csrfField,
8580
+ isRails: isRails
8581
+ };
8027
8582
  })(jQuery);
8028
8583
 
8029
8584
  }).call(this);