upjs-rails 0.12.5 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rdoc_options +23 -0
- data/CHANGELOG.md +20 -0
- data/design/up-validate.js.coffee +284 -0
- data/dist/up-bootstrap.js +4 -0
- data/dist/up-bootstrap.min.js +1 -1
- data/dist/up.js +547 -102
- data/dist/up.min.js +2 -2
- data/lib/assets/javascripts/up/browser.js.coffee +3 -2
- data/lib/assets/javascripts/up/flow.js.coffee +95 -17
- data/lib/assets/javascripts/up/form.js.coffee +327 -34
- data/lib/assets/javascripts/up/history.js.coffee +1 -1
- data/lib/assets/javascripts/up/layout.js.coffee +4 -4
- data/lib/assets/javascripts/up/link.js.coffee +5 -2
- data/lib/assets/javascripts/up/modal.js.coffee +1 -0
- data/lib/assets/javascripts/up/proxy.js.coffee +27 -12
- data/lib/assets/javascripts/up/syntax.js.coffee +39 -20
- data/lib/assets/javascripts/up/util.js.coffee +29 -12
- data/lib/assets/javascripts/up-bootstrap/form-ext.js.coffee +1 -0
- data/lib/upjs/rails/engine.rb +1 -1
- data/lib/upjs/rails/inspector.rb +63 -0
- data/lib/upjs/rails/inspector_accessor.rb +28 -0
- data/lib/upjs/rails/request_echo_headers.rb +7 -0
- data/lib/upjs/rails/request_method_cookie.rb +12 -4
- data/lib/upjs/rails/version.rb +5 -1
- data/lib/upjs-rails.rb +7 -5
- data/spec_app/.rspec +2 -0
- data/spec_app/Gemfile +0 -3
- data/spec_app/Gemfile.lock +43 -44
- data/spec_app/app/assets/stylesheets/application.css +1 -1
- data/spec_app/app/controllers/test_controller.rb +23 -0
- data/spec_app/config/routes.rb +2 -0
- data/spec_app/spec/controllers/test_controller_spec.rb +67 -0
- data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/last_request.js.coffee +18 -0
- data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +1 -0
- data/spec_app/spec/javascripts/up/flow_spec.js.coffee +93 -43
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +80 -18
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +1 -5
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +18 -17
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +32 -37
- data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +7 -26
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -7
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +26 -25
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +23 -0
- data/spec_app/spec/spec_helper.rb +62 -0
- metadata +12 -3
- data/lib/upjs/rails/request_ext.rb +0 -13
data/dist/up.js
CHANGED
@@ -25,7 +25,7 @@ If you use them in your own code, you will get hurt.
|
|
25
25
|
var slice = [].slice;
|
26
26
|
|
27
27
|
up.util = (function($) {
|
28
|
-
var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, any, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, emptyJQuery, endsWith, error, escapePressed, evalConsoleTemplate, extend, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, identity, ifGiven, 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, setMissingAttrs, startsWith, temporaryCss, times, toArray, trim, unJquery, uniq, unresolvablePromise, unwrapElement, warn;
|
28
|
+
var $createElementFromSelector, ANIMATION_PROMISE_KEY, CONSOLE_PLACEHOLDERS, ajax, any, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, createSelectorFromElement, cssAnimate, debug, detect, each, emptyJQuery, endsWith, error, escapePressed, evalConsoleTemplate, extend, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, identity, ifGiven, 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, setMissingAttrs, startsWith, temporaryCss, times, titleFromXhr, toArray, trim, unJquery, uniq, unresolvablePromise, unwrapElement, warn;
|
29
29
|
memoize = function(func) {
|
30
30
|
var cache, cached;
|
31
31
|
cache = void 0;
|
@@ -44,9 +44,8 @@ If you use them in your own code, you will get hurt.
|
|
44
44
|
ajax = function(request) {
|
45
45
|
request = copy(request);
|
46
46
|
if (request.selector) {
|
47
|
-
request.headers = {
|
48
|
-
|
49
|
-
};
|
47
|
+
request.headers || (request.headers = {});
|
48
|
+
request.headers['X-Up-Selector'] = request.selector;
|
50
49
|
}
|
51
50
|
return $.ajax(request);
|
52
51
|
};
|
@@ -261,18 +260,26 @@ If you use them in your own code, you will get hurt.
|
|
261
260
|
return arg;
|
262
261
|
});
|
263
262
|
};
|
264
|
-
createSelectorFromElement = function(
|
265
|
-
var classString, classes, id, j, klass, len, selector;
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
263
|
+
createSelectorFromElement = function(element) {
|
264
|
+
var $element, classString, classes, id, j, klass, len, name, selector, upId;
|
265
|
+
$element = $(element);
|
266
|
+
selector = void 0;
|
267
|
+
debug("Creating selector from element %o", $element.get(0));
|
268
|
+
if (upId = presence($element.attr("up-id"))) {
|
269
|
+
selector = "[up-id='" + upId + "']";
|
270
|
+
} else if (id = presence($element.attr("id"))) {
|
271
|
+
selector = "#" + id;
|
272
|
+
} else if (name = presence($element.attr("name"))) {
|
273
|
+
selector = "[name='" + name + "']";
|
274
|
+
} else if (classString = presence($element.attr("class"))) {
|
275
|
+
classes = classString.split(' ');
|
276
|
+
selector = '';
|
277
|
+
for (j = 0, len = classes.length; j < len; j++) {
|
278
|
+
klass = classes[j];
|
279
|
+
selector += "." + klass;
|
280
|
+
}
|
281
|
+
} else {
|
282
|
+
selector = $element.prop('tagName').toLowerCase();
|
276
283
|
}
|
277
284
|
return selector;
|
278
285
|
};
|
@@ -781,6 +788,9 @@ If you use them in your own code, you will get hurt.
|
|
781
788
|
locationFromXhr = function(xhr) {
|
782
789
|
return xhr.getResponseHeader('X-Up-Location');
|
783
790
|
};
|
791
|
+
titleFromXhr = function(xhr) {
|
792
|
+
return xhr.getResponseHeader('X-Up-Title');
|
793
|
+
};
|
784
794
|
methodFromXhr = function(xhr) {
|
785
795
|
return xhr.getResponseHeader('X-Up-Method');
|
786
796
|
};
|
@@ -936,6 +946,9 @@ If you use them in your own code, you will get hurt.
|
|
936
946
|
will be discarded.
|
937
947
|
@param {String} [config.log]
|
938
948
|
A prefix for log entries printed by this cache object.
|
949
|
+
@param {Function<Object>} [config.key]
|
950
|
+
A function that takes an argument and returns a `String` key
|
951
|
+
for storage. If omitted, `toString()` is called on the argument.
|
939
952
|
*/
|
940
953
|
cache = function(config) {
|
941
954
|
var alias, clear, expiryMilis, get, isFresh, keys, log, maxSize, normalizeStoreKey, set, store, timestamp;
|
@@ -1186,6 +1199,7 @@ If you use them in your own code, you will get hurt.
|
|
1186
1199
|
toArray: toArray,
|
1187
1200
|
castedAttr: castedAttr,
|
1188
1201
|
locationFromXhr: locationFromXhr,
|
1202
|
+
titleFromXhr: titleFromXhr,
|
1189
1203
|
methodFromXhr: methodFromXhr,
|
1190
1204
|
clientSize: clientSize,
|
1191
1205
|
only: only,
|
@@ -1325,8 +1339,9 @@ we can't currently get rid off.
|
|
1325
1339
|
Returns whether Up.js supports the current browser.
|
1326
1340
|
|
1327
1341
|
Currently Up.js supports IE9 with jQuery 1.9+.
|
1328
|
-
On older browsers Up.js will prevent itself from [booting](/up.boot)
|
1329
|
-
|
1342
|
+
On older browsers Up.js will prevent itself from [booting](/up.boot)
|
1343
|
+
and ignores all registered [event handlers](/up.on) and [compilers](/up.compiler).
|
1344
|
+
This leaves you with a classic server-side application.
|
1330
1345
|
|
1331
1346
|
@function up.browser.isSupported
|
1332
1347
|
*/
|
@@ -1661,18 +1676,36 @@ and call `preventDefault()` on the `event` object:
|
|
1661
1676
|
}).call(this);
|
1662
1677
|
|
1663
1678
|
/**
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1679
|
+
Enhancing elements
|
1680
|
+
==================
|
1681
|
+
|
1667
1682
|
Up.js keeps a persistent Javascript environment during page transitions.
|
1668
|
-
|
1669
|
-
|
1683
|
+
If you wire Javascript to run on `ready` or `onload` events, those scripts will
|
1684
|
+
only run during the initial page load. Subsequently [inserted](/up.replace)
|
1685
|
+
page fragments will not be compiled.
|
1670
1686
|
|
1671
|
-
|
1687
|
+
Let's say your Javascript plugin wants you to call `lightboxify()`
|
1688
|
+
on links that should open a lightbox. You decide to
|
1689
|
+
do this for all links with an `lightbox` class:
|
1672
1690
|
|
1673
|
-
|
1691
|
+
<a href="river.png" class="lightbox">River</a>
|
1692
|
+
<a href="ocean.png" class="lightbox">Ocean</a>
|
1674
1693
|
|
1675
|
-
|
1694
|
+
You should **avoid** doing this on page load:
|
1695
|
+
|
1696
|
+
$(document).on('ready', function() {
|
1697
|
+
$('a.lightbox').lightboxify();
|
1698
|
+
});
|
1699
|
+
|
1700
|
+
Instead you should register a [`compiler`](/up.compiler) for the `a.lightbox` selector:
|
1701
|
+
|
1702
|
+
up.compiler('a.lightbox', function($element) {
|
1703
|
+
$element.lightboxify();
|
1704
|
+
});
|
1705
|
+
|
1706
|
+
The compiler function will be called on matching elements when
|
1707
|
+
the page loads, or whenever a matching fragment is [updated through Up.js](/up.replace)
|
1708
|
+
later.
|
1676
1709
|
|
1677
1710
|
@class up.syntax
|
1678
1711
|
*/
|
@@ -1698,7 +1731,8 @@ We need to work on this page:
|
|
1698
1731
|
the page loads, or whenever a matching fragment is [updated through Up.js](/up.replace)
|
1699
1732
|
later.
|
1700
1733
|
|
1701
|
-
If you have used Angular.js before, this resembles
|
1734
|
+
If you have used Angular.js before, this resembles
|
1735
|
+
[Angular directives](https://docs.angularjs.org/guide/directive).
|
1702
1736
|
|
1703
1737
|
|
1704
1738
|
\#\#\#\# Integrating jQuery plugins
|
@@ -1706,14 +1740,14 @@ We need to work on this page:
|
|
1706
1740
|
`up.compiler` is a great way to integrate jQuery plugins.
|
1707
1741
|
Let's say your Javascript plugin wants you to call `lightboxify()`
|
1708
1742
|
on links that should open a lightbox. You decide to
|
1709
|
-
do this for all links with an `
|
1743
|
+
do this for all links with an `lightbox` class:
|
1710
1744
|
|
1711
|
-
<a href="river.png"
|
1712
|
-
<a href="ocean.png"
|
1745
|
+
<a href="river.png" class="lightbox">River</a>
|
1746
|
+
<a href="ocean.png" class="lightbox">Ocean</a>
|
1713
1747
|
|
1714
1748
|
This Javascript will do exactly that:
|
1715
1749
|
|
1716
|
-
up.compiler('a
|
1750
|
+
up.compiler('a.lightbox', function($element) {
|
1717
1751
|
$element.lightboxify();
|
1718
1752
|
});
|
1719
1753
|
|
@@ -1958,17 +1992,17 @@ We need to work on this page:
|
|
1958
1992
|
};
|
1959
1993
|
|
1960
1994
|
/**
|
1961
|
-
|
1962
|
-
|
1963
|
-
event listeners, etc.).
|
1995
|
+
Compiles a page fragment that has been inserted into the DOM
|
1996
|
+
without Up.js.
|
1964
1997
|
|
1965
1998
|
**As long as you manipulate the DOM using Up.js, you will never
|
1966
1999
|
need to call this method.** You only need to use `up.hello` if the
|
1967
|
-
DOM is manipulated without Up.js' involvement, e.g. by
|
1968
|
-
|
2000
|
+
DOM is manipulated without Up.js' involvement, e.g. by setting
|
2001
|
+
the `innerHTML` property or calling jQuery methods like
|
2002
|
+
`html`, `insertAfter` or `appendTo`:
|
1969
2003
|
|
1970
|
-
|
1971
|
-
$element
|
2004
|
+
$element = $('.element');
|
2005
|
+
$element.html('<div>...</div>');
|
1972
2006
|
up.hello($element);
|
1973
2007
|
|
1974
2008
|
This function emits the [`up:fragment:inserted`](/up:fragment:inserted)
|
@@ -2054,7 +2088,7 @@ We need to work on this page:
|
|
2054
2088
|
|
2055
2089
|
/**
|
2056
2090
|
@property up.history.config
|
2057
|
-
@param {Array
|
2091
|
+
@param {Array} [config.popTargets=['body']]
|
2058
2092
|
An array of CSS selectors to replace when the user goes
|
2059
2093
|
back in history.
|
2060
2094
|
@param {Boolean} [config.restoreScroll=true]
|
@@ -2260,16 +2294,16 @@ This modules contains functions to scroll the viewport and reveal contained elem
|
|
2260
2294
|
Configures the application layout.
|
2261
2295
|
|
2262
2296
|
@property up.layout.config
|
2263
|
-
@param {Array
|
2297
|
+
@param {Array} [config.viewports]
|
2264
2298
|
An array of CSS selectors that find viewports
|
2265
2299
|
(containers that scroll their contents).
|
2266
|
-
@param {Array
|
2300
|
+
@param {Array} [config.fixedTop]
|
2267
2301
|
An array of CSS selectors that find elements fixed to the
|
2268
2302
|
top edge of the screen (using `position: fixed`).
|
2269
|
-
@param {Array
|
2303
|
+
@param {Array} [config.fixedBottom]
|
2270
2304
|
An array of CSS selectors that find elements fixed to the
|
2271
2305
|
bottom edge of the screen (using `position: fixed`).
|
2272
|
-
@param {Array
|
2306
|
+
@param {Array} [config.anchoredRight]
|
2273
2307
|
An array of CSS selectors that find elements anchored to the
|
2274
2308
|
right edge of the screen (using `position: fixed` or `position: absolute`).
|
2275
2309
|
@param {Number} [config.duration]
|
@@ -2838,7 +2872,7 @@ are based on this module.
|
|
2838
2872
|
|
2839
2873
|
(function() {
|
2840
2874
|
up.flow = (function($) {
|
2841
|
-
var autofocus, destroy, elementsInserted, findOldFragment, first, fragmentNotFound, implant, isRealElement, parseImplantSteps, parseResponse, reload, replace, setSource, source, swapElements, u;
|
2875
|
+
var autofocus, destroy, elementsInserted, findOldFragment, first, fragmentNotFound, implant, isRealElement, parseImplantSteps, parseResponse, reload, replace, resolveSelector, setSource, source, swapElements, u;
|
2842
2876
|
u = up.util;
|
2843
2877
|
setSource = function(element, sourceUrl) {
|
2844
2878
|
var $element;
|
@@ -2854,6 +2888,28 @@ are based on this module.
|
|
2854
2888
|
return u.presence($element.attr("up-source")) || up.browser.url();
|
2855
2889
|
};
|
2856
2890
|
|
2891
|
+
/**
|
2892
|
+
@function up.flow.resolveSelector
|
2893
|
+
@private
|
2894
|
+
*/
|
2895
|
+
resolveSelector = function(selectorOrElement, options) {
|
2896
|
+
var origin, originSelector, selector;
|
2897
|
+
if (u.isString(selectorOrElement)) {
|
2898
|
+
selector = selectorOrElement;
|
2899
|
+
if (u.contains(selector, '&')) {
|
2900
|
+
if (origin = u.presence(options.origin)) {
|
2901
|
+
originSelector = u.createSelectorFromElement(origin);
|
2902
|
+
selector = selector.replace(/\&/, originSelector);
|
2903
|
+
} else {
|
2904
|
+
u.error("Found origin reference %o in selector %o, but options.origin is missing", '&', selector);
|
2905
|
+
}
|
2906
|
+
}
|
2907
|
+
} else {
|
2908
|
+
selector = u.createSelectorFromElement(selectorOrElement);
|
2909
|
+
}
|
2910
|
+
return selector;
|
2911
|
+
};
|
2912
|
+
|
2857
2913
|
/**
|
2858
2914
|
Replaces elements on the current page with corresponding elements
|
2859
2915
|
from a new page fetched from the server.
|
@@ -2885,6 +2941,41 @@ are based on this module.
|
|
2885
2941
|
Note how only `.two` has changed. The update for `.one` was
|
2886
2942
|
discarded, since it didn't match the selector.
|
2887
2943
|
|
2944
|
+
\#\#\#\# Appending or prepending instead of replacing
|
2945
|
+
|
2946
|
+
By default Up.js will replace the given selector with the same
|
2947
|
+
selector from a freshly fetched page. Instead of replacing you
|
2948
|
+
can *append* the loaded content to the existing content by using the
|
2949
|
+
`:after` pseudo selector. In the same fashion, you can use `:before`
|
2950
|
+
to indicate that you would like the *prepend* the loaded content.
|
2951
|
+
|
2952
|
+
A practical example would be a paginated list of items:
|
2953
|
+
|
2954
|
+
<ul class="tasks">
|
2955
|
+
<li>Wash car</li>
|
2956
|
+
<li>Purchase supplies</li>
|
2957
|
+
<li>Fix tent</li>
|
2958
|
+
</ul>
|
2959
|
+
|
2960
|
+
In order to append more items from a URL, replace into
|
2961
|
+
the `.tasks:after` selector:
|
2962
|
+
|
2963
|
+
up.replace('.tasks:after', '/page/2')
|
2964
|
+
|
2965
|
+
\#\#\#\# Setting the window title from the server
|
2966
|
+
|
2967
|
+
If the `replace` call changes history, the document title will be set
|
2968
|
+
to the contents of a `<title>` tag in the response.
|
2969
|
+
|
2970
|
+
The server can also change the document title by setting
|
2971
|
+
an `X-Up-Title` header in the response.
|
2972
|
+
|
2973
|
+
\#\#\#\# Optimizing response rendering
|
2974
|
+
|
2975
|
+
The server is free to optimize Up.js requests by only rendering the HTML fragment
|
2976
|
+
that is being updated. The request's `X-Up-Selector` header will contain
|
2977
|
+
the CSS selector for the updating fragment.
|
2978
|
+
|
2888
2979
|
\#\#\#\# Events
|
2889
2980
|
|
2890
2981
|
Up.js will emit [`up:fragment:destroyed`](/up:fragment:destroyed) on the element
|
@@ -2915,7 +3006,13 @@ are based on this module.
|
|
2915
3006
|
history change for the current URL.
|
2916
3007
|
@param {Boolean} [options.cache]
|
2917
3008
|
Whether to use a [cached response](/up.proxy) if available.
|
3009
|
+
@param {Element|jQuery} [options.origin]
|
3010
|
+
The element that triggered the replacement. The element's selector will
|
3011
|
+
be substituted for the `&` shorthand in the target selector.
|
2918
3012
|
@param {String} [options.historyMethod='push']
|
3013
|
+
@param {Object} [options.headers={}]
|
3014
|
+
An object of additional header key/value pairs to send along
|
3015
|
+
with the request.
|
2919
3016
|
@return {Promise}
|
2920
3017
|
A promise that will be resolved when the page has been updated.
|
2921
3018
|
*/
|
@@ -2923,7 +3020,7 @@ are based on this module.
|
|
2923
3020
|
var promise, request, selector;
|
2924
3021
|
u.debug("Replace %o with %o (options %o)", selectorOrElement, url, options);
|
2925
3022
|
options = u.options(options);
|
2926
|
-
selector =
|
3023
|
+
selector = resolveSelector(selectorOrElement, options);
|
2927
3024
|
if (!up.browser.canPushState() && options.history !== false) {
|
2928
3025
|
if (!options.preload) {
|
2929
3026
|
up.browser.loadPage(url, u.only(options, 'method'));
|
@@ -2935,7 +3032,8 @@ are based on this module.
|
|
2935
3032
|
method: options.method,
|
2936
3033
|
selector: selector,
|
2937
3034
|
cache: options.cache,
|
2938
|
-
preload: options.preload
|
3035
|
+
preload: options.preload,
|
3036
|
+
headers: options.headers
|
2939
3037
|
};
|
2940
3038
|
promise = up.proxy.ajax(request);
|
2941
3039
|
promise.done(function(html, textStatus, xhr) {
|
@@ -2956,6 +3054,7 @@ are based on this module.
|
|
2956
3054
|
if (options.source !== false) {
|
2957
3055
|
options.source = url;
|
2958
3056
|
}
|
3057
|
+
options.title || (options.title = u.titleFromXhr(xhr));
|
2959
3058
|
if (!options.preload) {
|
2960
3059
|
return implant(selector, html, options);
|
2961
3060
|
}
|
@@ -2994,13 +3093,14 @@ are based on this module.
|
|
2994
3093
|
|
2995
3094
|
@function up.flow.implant
|
2996
3095
|
@protected
|
2997
|
-
@param {String}
|
3096
|
+
@param {String|Element|jQuery} selectorOrElement
|
2998
3097
|
@param {String} html
|
2999
3098
|
@param {Object} [options]
|
3000
3099
|
See options for [`up.replace`](/up.replace).
|
3001
3100
|
*/
|
3002
|
-
implant = function(
|
3003
|
-
var $new, $old, j, len, ref, response, results, step;
|
3101
|
+
implant = function(selectorOrElement, html, options) {
|
3102
|
+
var $new, $old, j, len, ref, response, results, selector, step;
|
3103
|
+
selector = resolveSelector(selectorOrElement, options);
|
3004
3104
|
options = u.options(options, {
|
3005
3105
|
historyMethod: 'push'
|
3006
3106
|
});
|
@@ -3041,7 +3141,7 @@ are based on this module.
|
|
3041
3141
|
},
|
3042
3142
|
find: function(selector) {
|
3043
3143
|
var child;
|
3044
|
-
if (child =
|
3144
|
+
if (child = $.find(selector, htmlElement)[0]) {
|
3045
3145
|
return $(child);
|
3046
3146
|
} else {
|
3047
3147
|
return u.error("Could not find selector %o in response %o", selector, html);
|
@@ -3094,7 +3194,7 @@ are based on this module.
|
|
3094
3194
|
}
|
3095
3195
|
};
|
3096
3196
|
parseImplantSteps = function(selector, options) {
|
3097
|
-
var comma, disjunction, i, j, len, results, selectorAtom, selectorParts, transition, transitionString, transitions;
|
3197
|
+
var comma, disjunction, i, j, len, pseudoClass, results, selectorAtom, selectorParts, transition, transitionString, transitions;
|
3098
3198
|
transitionString = options.transition || options.animation || 'none';
|
3099
3199
|
comma = /\ *,\ */;
|
3100
3200
|
disjunction = selector.split(comma);
|
@@ -3105,10 +3205,15 @@ are based on this module.
|
|
3105
3205
|
for (i = j = 0, len = disjunction.length; j < len; i = ++j) {
|
3106
3206
|
selectorAtom = disjunction[i];
|
3107
3207
|
selectorParts = selectorAtom.match(/^(.+?)(?:\:(before|after))?$/);
|
3208
|
+
selector = selectorParts[1];
|
3209
|
+
if (selector === 'html') {
|
3210
|
+
selector = 'body';
|
3211
|
+
}
|
3212
|
+
pseudoClass = selectorParts[2];
|
3108
3213
|
transition = transitions[i] || u.last(transitions);
|
3109
3214
|
results.push({
|
3110
|
-
selector:
|
3111
|
-
pseudoClass:
|
3215
|
+
selector: selector,
|
3216
|
+
pseudoClass: pseudoClass,
|
3112
3217
|
transition: transition
|
3113
3218
|
});
|
3114
3219
|
}
|
@@ -3134,15 +3239,27 @@ are based on this module.
|
|
3134
3239
|
Excludes elements that also match `.up-ghost` or `.up-destroying`
|
3135
3240
|
or that are children of elements with these selectors.
|
3136
3241
|
|
3242
|
+
If the given argument is already a jQuery collection (or an array
|
3243
|
+
of DOM elements), the first element matching these conditions
|
3244
|
+
is returned.
|
3245
|
+
|
3137
3246
|
Returns `undefined` if no element matches these conditions.
|
3138
3247
|
|
3139
3248
|
@protected
|
3140
3249
|
@function up.first
|
3141
|
-
@param {String}
|
3250
|
+
@param {String|Element|jQuery} selectorOrElement
|
3251
|
+
@return {jQuery}
|
3252
|
+
The first element that is neither a ghost or being destroyed,
|
3253
|
+
or `undefined` if no such element was given.
|
3142
3254
|
*/
|
3143
|
-
first = function(
|
3255
|
+
first = function(selectorOrElement) {
|
3144
3256
|
var $element, $match, element, elements, j, len;
|
3145
|
-
elements =
|
3257
|
+
elements = void 0;
|
3258
|
+
if (u.isString(selectorOrElement)) {
|
3259
|
+
elements = $(selectorOrElement).get();
|
3260
|
+
} else {
|
3261
|
+
elements = selectorOrElement;
|
3262
|
+
}
|
3146
3263
|
$match = void 0;
|
3147
3264
|
for (j = 0, len = elements.length; j < len; j++) {
|
3148
3265
|
element = elements[j];
|
@@ -3273,7 +3390,8 @@ are based on this module.
|
|
3273
3390
|
reload: reload,
|
3274
3391
|
destroy: destroy,
|
3275
3392
|
implant: implant,
|
3276
|
-
first: first
|
3393
|
+
first: first,
|
3394
|
+
resolveSelector: resolveSelector
|
3277
3395
|
};
|
3278
3396
|
})(jQuery);
|
3279
3397
|
|
@@ -4024,7 +4142,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4024
4142
|
The number of milliseconds until a cache entry expires.
|
4025
4143
|
Defaults to 5 minutes.
|
4026
4144
|
@param {Number} [config.busyDelay=300]
|
4027
|
-
How long the proxy waits until emitting the `proxy:busy`
|
4145
|
+
How long the proxy waits until emitting the [`up:proxy:busy` event](/up:proxy:busy).
|
4028
4146
|
Use this to prevent flickering of spinners.
|
4029
4147
|
*/
|
4030
4148
|
config = u.config({
|
@@ -4052,7 +4170,29 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4052
4170
|
@protected
|
4053
4171
|
@function up.proxy.get
|
4054
4172
|
*/
|
4055
|
-
get =
|
4173
|
+
get = function(request) {
|
4174
|
+
var candidate, candidates, i, len, requestForBody, requestForHtml, response;
|
4175
|
+
request = normalizeRequest(request);
|
4176
|
+
candidates = [request];
|
4177
|
+
if (request.selector !== 'html') {
|
4178
|
+
requestForHtml = u.merge(request, {
|
4179
|
+
selector: 'html'
|
4180
|
+
});
|
4181
|
+
candidates.push(requestForHtml);
|
4182
|
+
if (request.selector !== 'body') {
|
4183
|
+
requestForBody = u.merge(request, {
|
4184
|
+
selector: 'body'
|
4185
|
+
});
|
4186
|
+
candidates.push(requestForBody);
|
4187
|
+
}
|
4188
|
+
}
|
4189
|
+
for (i = 0, len = candidates.length; i < len; i++) {
|
4190
|
+
candidate = candidates[i];
|
4191
|
+
if (response = cache.get(candidate)) {
|
4192
|
+
return response;
|
4193
|
+
}
|
4194
|
+
}
|
4195
|
+
};
|
4056
4196
|
|
4057
4197
|
/**
|
4058
4198
|
@protected
|
@@ -4113,8 +4253,8 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4113
4253
|
are considered to be read-only.
|
4114
4254
|
|
4115
4255
|
If a network connection is attempted, the proxy will emit
|
4116
|
-
a `proxy:load` event with the `request` as its argument.
|
4117
|
-
Once the response is received, a `proxy:receive` event will
|
4256
|
+
a `up:proxy:load` event with the `request` as its argument.
|
4257
|
+
Once the response is received, a `up:proxy:receive` event will
|
4118
4258
|
be emitted.
|
4119
4259
|
|
4120
4260
|
@function up.proxy.ajax
|
@@ -4124,12 +4264,15 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4124
4264
|
@param {Boolean} [request.cache]
|
4125
4265
|
Whether to use a cached response, if available.
|
4126
4266
|
If set to `false` a network connection will always be attempted.
|
4267
|
+
@param {Object} [request.headers={}]
|
4268
|
+
An object of additional header key/value pairs to send along
|
4269
|
+
with the request.
|
4127
4270
|
*/
|
4128
4271
|
ajax = function(options) {
|
4129
4272
|
var forceCache, ignoreCache, pending, promise, request;
|
4130
4273
|
forceCache = options.cache === true;
|
4131
4274
|
ignoreCache = options.cache === false;
|
4132
|
-
request = u.only(options, 'url', 'method', 'data', 'selector', '_normalized');
|
4275
|
+
request = u.only(options, 'url', 'method', 'data', 'selector', 'headers', '_normalized');
|
4133
4276
|
pending = true;
|
4134
4277
|
if (!isIdempotent(request) && !forceCache) {
|
4135
4278
|
clear();
|
@@ -4155,7 +4298,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4155
4298
|
Returns `true` if the proxy is not currently waiting
|
4156
4299
|
for a request to finish. Returns `false` otherwise.
|
4157
4300
|
|
4158
|
-
The proxy will also emit an `proxy:idle`
|
4301
|
+
The proxy will also emit an [`up:proxy:idle` event](/up:proxy:idle) if it
|
4159
4302
|
used to busy, but is now idle.
|
4160
4303
|
|
4161
4304
|
@function up.proxy.idle
|
@@ -4169,8 +4312,8 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4169
4312
|
Returns `true` if the proxy is currently waiting
|
4170
4313
|
for a request to finish. Returns `false` otherwise.
|
4171
4314
|
|
4172
|
-
The proxy will also emit an `proxy:busy`
|
4173
|
-
used to idle, but is now busy.
|
4315
|
+
The proxy will also emit an [`up:proxy:busy` event](/up:proxy:busy) if it
|
4316
|
+
used to be idle, but is now busy.
|
4174
4317
|
|
4175
4318
|
@function up.proxy.busy
|
4176
4319
|
@return {Boolean} Whether the proxy is busy
|
@@ -4226,7 +4369,7 @@ You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.conf
|
|
4226
4369
|
This event is [emitted]/(up.emit) when [AJAX requests](/up.proxy.ajax)
|
4227
4370
|
have [taken long to finish](/up:proxy:busy), but have finished now.
|
4228
4371
|
|
4229
|
-
@event up:proxy:
|
4372
|
+
@event up:proxy:idle
|
4230
4373
|
*/
|
4231
4374
|
load = function(request) {
|
4232
4375
|
var promise;
|
@@ -4488,12 +4631,15 @@ Read on
|
|
4488
4631
|
@param {Element|jQuery|String} [options.reveal]
|
4489
4632
|
Whether to reveal the target element within its viewport before updating.
|
4490
4633
|
@param {Boolean} [options.restoreScroll]
|
4491
|
-
If set to `true`, this will attempt to [
|
4634
|
+
If set to `true`, this will attempt to [restore scroll positions](/up.restoreScroll)
|
4492
4635
|
previously seen on the destination URL.
|
4493
4636
|
@param {Boolean} [options.cache]
|
4494
4637
|
Whether to force the use of a cached response (`true`)
|
4495
4638
|
or never use the cache (`false`)
|
4496
4639
|
or make an educated guess (`undefined`).
|
4640
|
+
@param {Object} [options.headers={}]
|
4641
|
+
An object of additional header key/value pairs to send along
|
4642
|
+
with the request.
|
4497
4643
|
*/
|
4498
4644
|
follow = function(linkOrSelector, options) {
|
4499
4645
|
var $link, selector, url;
|
@@ -4507,6 +4653,7 @@ Read on
|
|
4507
4653
|
options.cache = u.option(options.cache, u.castedAttr($link, 'up-cache'));
|
4508
4654
|
options.restoreScroll = u.option(options.restoreScroll, u.castedAttr($link, 'up-restore-scroll'));
|
4509
4655
|
options.method = followMethod($link, options);
|
4656
|
+
options.origin = u.option(options.origin, $link);
|
4510
4657
|
options = u.merge(options, up.motion.animateOptions(options, $link));
|
4511
4658
|
return up.replace(selector, url, options);
|
4512
4659
|
};
|
@@ -4786,41 +4933,54 @@ Read on
|
|
4786
4933
|
}).call(this);
|
4787
4934
|
|
4788
4935
|
/**
|
4789
|
-
Forms
|
4790
|
-
|
4936
|
+
Forms
|
4937
|
+
=====
|
4791
4938
|
|
4792
|
-
Up.js comes with functionality to submit
|
4793
|
-
leaving the current page. This means you can replace page fragments,
|
4939
|
+
Up.js comes with functionality to [submit](/form-up-target) and [validate](/up-validate)
|
4940
|
+
forms without leaving the current page. This means you can replace page fragments,
|
4794
4941
|
open dialogs with sub-forms, etc. all without losing form state.
|
4795
|
-
|
4796
|
-
\#\#\# Incomplete documentation!
|
4797
|
-
|
4798
|
-
We need to work on this page:
|
4799
|
-
|
4800
|
-
- Explain how to display form errors
|
4801
|
-
- Explain that the server needs to send 2xx or 5xx status codes so
|
4802
|
-
Up.js can decide whether the form submission was successful
|
4803
|
-
- Explain that the server needs to send `X-Up-Location` and `X-Up-Method` headers
|
4804
|
-
if an successful form submission resulted in a redirect
|
4805
|
-
- Examples
|
4806
4942
|
|
4807
4943
|
@class up.form
|
4808
4944
|
*/
|
4809
4945
|
|
4810
4946
|
(function() {
|
4811
4947
|
up.form = (function($) {
|
4812
|
-
var observe, submit, u;
|
4948
|
+
var config, observe, reset, resolveValidateTarget, submit, u, validate;
|
4813
4949
|
u = up.util;
|
4814
4950
|
|
4815
4951
|
/**
|
4816
|
-
|
4952
|
+
Sets default options for form submission and validation.
|
4953
|
+
|
4954
|
+
@property up.form.config
|
4955
|
+
@param {Array} [config.validateTargets=['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']]
|
4956
|
+
An array of CSS selectors that are searched around a form field
|
4957
|
+
that wants to [validate](/up.validate). The first matching selector
|
4958
|
+
will be updated with the validation messages from the server.
|
4959
|
+
|
4960
|
+
By default this looks for a `<fieldset>`, `<label>` or `<form>`
|
4961
|
+
around the validating input field, or any element with an
|
4962
|
+
`up-fieldset` attribute.
|
4963
|
+
*/
|
4964
|
+
config = u.config({
|
4965
|
+
validateTargets: ['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']
|
4966
|
+
});
|
4967
|
+
reset = function() {
|
4968
|
+
return config.reset();
|
4969
|
+
};
|
4970
|
+
|
4971
|
+
/**
|
4972
|
+
Submits a form via AJAX and updates a page fragment with the response.
|
4817
4973
|
|
4818
|
-
up.submit('form.
|
4974
|
+
up.submit('form.new-user', { target: '.main' })
|
4819
4975
|
|
4820
4976
|
Instead of loading a new page, the form is submitted via AJAX.
|
4821
4977
|
The response is parsed for a CSS selector and the matching elements will
|
4822
4978
|
replace corresponding elements on the current page.
|
4823
4979
|
|
4980
|
+
The UJS variant of this is the [`form[up-target]`](/form-up-target) selector.
|
4981
|
+
See the documentation for [`form[up-target]`](/form-up-target) for more
|
4982
|
+
information on how AJAX form submissions work in Up.js.
|
4983
|
+
|
4824
4984
|
@function up.submit
|
4825
4985
|
@param {Element|jQuery|String} formOrSelector
|
4826
4986
|
A reference or selector for the form to submit.
|
@@ -4870,39 +5030,52 @@ We need to work on this page:
|
|
4870
5030
|
|
4871
5031
|
By default only responses to `GET` requests are cached
|
4872
5032
|
for a few minutes.
|
5033
|
+
@param {Object} [options.headers={}]
|
5034
|
+
An object of additional header key/value pairs to send along
|
5035
|
+
with the request.
|
4873
5036
|
@return {Promise}
|
4874
5037
|
A promise for the successful form submission.
|
4875
5038
|
*/
|
4876
5039
|
submit = function(formOrSelector, options) {
|
4877
|
-
var $form, failureSelector, failureTransition, historyOption, httpMethod, implantOptions, request, successSelector, successTransition, successUrl, url, useCache;
|
5040
|
+
var $form, failureSelector, failureTransition, hasFileInputs, headers, historyOption, httpMethod, implantOptions, request, successSelector, successTransition, successUrl, url, useCache;
|
4878
5041
|
$form = $(formOrSelector).closest('form');
|
4879
5042
|
options = u.options(options);
|
4880
|
-
successSelector = u.option(options.target, $form.attr('up-target'), 'body');
|
4881
|
-
failureSelector = u.option(options.failTarget, $form.attr('up-fail-target'), function() {
|
5043
|
+
successSelector = up.flow.resolveSelector(u.option(options.target, $form.attr('up-target'), 'body'), options);
|
5044
|
+
failureSelector = up.flow.resolveSelector(u.option(options.failTarget, $form.attr('up-fail-target'), function() {
|
4882
5045
|
return u.createSelectorFromElement($form);
|
4883
|
-
});
|
5046
|
+
}), options);
|
4884
5047
|
historyOption = u.option(options.history, u.castedAttr($form, 'up-history'), true);
|
4885
5048
|
successTransition = u.option(options.transition, u.castedAttr($form, 'up-transition'));
|
4886
5049
|
failureTransition = u.option(options.failTransition, u.castedAttr($form, 'up-fail-transition'), successTransition);
|
4887
5050
|
httpMethod = u.option(options.method, $form.attr('up-method'), $form.attr('data-method'), $form.attr('method'), 'post').toUpperCase();
|
5051
|
+
headers = u.option(options.headers, {});
|
4888
5052
|
implantOptions = {};
|
4889
5053
|
implantOptions.reveal = u.option(options.reveal, u.castedAttr($form, 'up-reveal'), true);
|
4890
5054
|
implantOptions.cache = u.option(options.cache, u.castedAttr($form, 'up-cache'));
|
4891
5055
|
implantOptions.restoreScroll = u.option(options.restoreScroll, u.castedAttr($form, 'up-restore-scroll'));
|
5056
|
+
implantOptions.origin = u.option(options.origin, $form);
|
4892
5057
|
implantOptions = u.extend(implantOptions, up.motion.animateOptions(options, $form));
|
4893
5058
|
useCache = u.option(options.cache, u.castedAttr($form, 'up-cache'));
|
4894
5059
|
url = u.option(options.url, $form.attr('action'), up.browser.url());
|
5060
|
+
hasFileInputs = $form.find('input[type=file]').length;
|
5061
|
+
if (options.validate) {
|
5062
|
+
headers['X-Up-Validate'] = options.validate;
|
5063
|
+
if (hasFileInputs) {
|
5064
|
+
return u.unresolvablePromise();
|
5065
|
+
}
|
5066
|
+
}
|
4895
5067
|
$form.addClass('up-active');
|
4896
|
-
if (!up.browser.canPushState() && historyOption !== false) {
|
5068
|
+
if (hasFileInputs || (!up.browser.canPushState() && historyOption !== false)) {
|
4897
5069
|
$form.get(0).submit();
|
4898
|
-
return;
|
5070
|
+
return u.unresolvablePromise();
|
4899
5071
|
}
|
4900
5072
|
request = {
|
4901
5073
|
url: url,
|
4902
5074
|
method: httpMethod,
|
4903
5075
|
data: $form.serialize(),
|
4904
5076
|
selector: successSelector,
|
4905
|
-
cache: useCache
|
5077
|
+
cache: useCache,
|
5078
|
+
headers: headers
|
4906
5079
|
};
|
4907
5080
|
successUrl = function(xhr) {
|
4908
5081
|
var currentLocation;
|
@@ -4941,7 +5114,11 @@ We need to work on this page:
|
|
4941
5114
|
Observes a form field and runs a callback when its value changes.
|
4942
5115
|
This is useful for observing text fields while the user is typing.
|
4943
5116
|
|
4944
|
-
|
5117
|
+
The UJS variant of this is the [`up-observe`](/up-observe) attribute.
|
5118
|
+
|
5119
|
+
\#\#\#\# Example
|
5120
|
+
|
5121
|
+
The following would submit the form whenever the
|
4945
5122
|
text field value changes:
|
4946
5123
|
|
4947
5124
|
up.observe('input[name=query]', { change: function(value, $input) {
|
@@ -4969,7 +5146,6 @@ We need to work on this page:
|
|
4969
5146
|
change: function(value, $input) { up.submit($input) }
|
4970
5147
|
});
|
4971
5148
|
|
4972
|
-
|
4973
5149
|
@function up.observe
|
4974
5150
|
@param {Element|jQuery|String} fieldOrSelector
|
4975
5151
|
@param {Function(value, $field)|String} options.change
|
@@ -5043,15 +5219,131 @@ We need to work on this page:
|
|
5043
5219
|
check();
|
5044
5220
|
return clearTimer;
|
5045
5221
|
};
|
5222
|
+
resolveValidateTarget = function($field, options) {
|
5223
|
+
var target;
|
5224
|
+
target = u.option(options.target, $field.attr('up-validate'));
|
5225
|
+
if (u.isBlank(target)) {
|
5226
|
+
target || (target = u.detect(config.validateTargets, function(defaultTarget) {
|
5227
|
+
var resolvedDefault;
|
5228
|
+
resolvedDefault = up.flow.resolveSelector(defaultTarget, options);
|
5229
|
+
return $field.closest(resolvedDefault).length;
|
5230
|
+
}));
|
5231
|
+
}
|
5232
|
+
if (u.isBlank(target)) {
|
5233
|
+
error('Could not find default validation target for %o (tried ancestors %o)', $field, config.validateTargets);
|
5234
|
+
}
|
5235
|
+
if (!u.isString(target)) {
|
5236
|
+
target = u.createSelectorFromElement(target);
|
5237
|
+
}
|
5238
|
+
return target;
|
5239
|
+
};
|
5240
|
+
|
5241
|
+
/**
|
5242
|
+
Performs a server-side validation of a form and update the form
|
5243
|
+
with validation messages.
|
5244
|
+
|
5245
|
+
`up.validate` submits the given field's form with an additional `X-Up-Validate`
|
5246
|
+
HTTP header. Upon seeing this header, the server is expected to validate (but not save)
|
5247
|
+
the form submission and render a new copy of the form with validation errors.
|
5248
|
+
|
5249
|
+
The UJS variant of this is the [`[up-validate]`](/up-validate) selector.
|
5250
|
+
See the documentation for [`[up-validate]`](/up-validate) for more information
|
5251
|
+
on how server-side validation works in Up.js.
|
5252
|
+
|
5253
|
+
\#\#\#\# Example
|
5254
|
+
|
5255
|
+
up.validate('input[name=email]', { target: '.email-errors' })
|
5256
|
+
|
5257
|
+
@function up.validate
|
5258
|
+
@param {String|Element|jQuery} fieldOrSelector
|
5259
|
+
@param {String|Element|jQuery} [options.target]
|
5260
|
+
@return {Promise}
|
5261
|
+
A promise that is resolved when the server-side
|
5262
|
+
validation is received and the form was updated.
|
5263
|
+
*/
|
5264
|
+
validate = function(fieldOrSelector, options) {
|
5265
|
+
var $field, $form, promise;
|
5266
|
+
$field = $(fieldOrSelector);
|
5267
|
+
options = u.options(options);
|
5268
|
+
options.origin = $field;
|
5269
|
+
options.target = resolveValidateTarget($field, options);
|
5270
|
+
options.failTarget = options.target;
|
5271
|
+
options.history = false;
|
5272
|
+
options.headers = u.option(options.headers, {});
|
5273
|
+
options.validate = $field.attr('name') || '__none__';
|
5274
|
+
options = u.merge(options, up.motion.animateOptions(options, $field));
|
5275
|
+
$form = $field.closest('form');
|
5276
|
+
promise = up.submit($form, options);
|
5277
|
+
return promise;
|
5278
|
+
};
|
5046
5279
|
|
5047
5280
|
/**
|
5048
|
-
|
5049
|
-
|
5281
|
+
Forms with an `up-target` attribute are [submitted via AJAX](/up.submit)
|
5282
|
+
instead of triggering a full page reload.
|
5050
5283
|
|
5051
5284
|
<form method="post" action="/users" up-target=".main">
|
5052
5285
|
...
|
5053
5286
|
</form>
|
5054
5287
|
|
5288
|
+
The server response is searched for the selector given in `up-target`.
|
5289
|
+
The selector content is then [replaced](/up.replace) in the current page.
|
5290
|
+
|
5291
|
+
The programmatic variant of this is the [`up.submit`](/up.submit) function.
|
5292
|
+
|
5293
|
+
\#\#\#\# Validation errors
|
5294
|
+
|
5295
|
+
When the server was unable to save the form due to invalid data,
|
5296
|
+
it will usually re-render an updated copy of the form with
|
5297
|
+
validation messages.
|
5298
|
+
|
5299
|
+
For Up.js to be able to pick up a validation failure,
|
5300
|
+
the form must be re-rendered with a non-200 HTTP status code.
|
5301
|
+
We recommend to use either 400 (bad request) or
|
5302
|
+
422 (unprocessable entity).
|
5303
|
+
|
5304
|
+
In Ruby on Rails, you can pass a
|
5305
|
+
[`:status` option to `render`](http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option)
|
5306
|
+
for this:
|
5307
|
+
|
5308
|
+
class UsersController < ApplicationController
|
5309
|
+
|
5310
|
+
def create
|
5311
|
+
user_params = params[:user].permit(:email, :password)
|
5312
|
+
@user = User.new(user_params)
|
5313
|
+
if @user.save?
|
5314
|
+
sign_in @user
|
5315
|
+
else
|
5316
|
+
render 'form', status: :bad_request
|
5317
|
+
end
|
5318
|
+
end
|
5319
|
+
|
5320
|
+
end
|
5321
|
+
|
5322
|
+
Note that you can also use the
|
5323
|
+
[`up-validate`](/up-validate) attribute to perform server-side
|
5324
|
+
validations while the user is completing fields.
|
5325
|
+
|
5326
|
+
\#\#\#\# Redirects
|
5327
|
+
|
5328
|
+
Up.js requires two additional response headers to detect redirects,
|
5329
|
+
which are otherwise undetectable for an AJAX client.
|
5330
|
+
|
5331
|
+
When the form's action performs a redirect, the server should echo
|
5332
|
+
the new request's URL as a response header `X-Up-Location`
|
5333
|
+
and the request's HTTP method as `X-Up-Method`.
|
5334
|
+
|
5335
|
+
If you are using Up.js via the `upjs-rails` gem, these headers
|
5336
|
+
are set automatically for every request.
|
5337
|
+
|
5338
|
+
\#\#\#\# Giving feedback while the form is processing
|
5339
|
+
|
5340
|
+
The `<form>` element will be assigned a CSS class `up-active` while
|
5341
|
+
the submission is loading.
|
5342
|
+
|
5343
|
+
You can also [implement a spinner](/up.proxy/#spinners)
|
5344
|
+
by [listening](/up.on) to the [`up:proxy:busy`](/up:proxy:busy)
|
5345
|
+
and [`up:proxy:idle`](/up:proxy:idle) events.
|
5346
|
+
|
5055
5347
|
@selector form[up-target]
|
5056
5348
|
@param {String} up-target
|
5057
5349
|
The selector to [replace](/up.replace) if the form submission is successful (200 status code).
|
@@ -5086,11 +5378,161 @@ We need to work on this page:
|
|
5086
5378
|
return submit($form);
|
5087
5379
|
});
|
5088
5380
|
|
5381
|
+
/**
|
5382
|
+
When a form field with this attribute is changed,
|
5383
|
+
the form is validated on the server and is updated with
|
5384
|
+
validation messages.
|
5385
|
+
|
5386
|
+
The programmatic variant of this is the [`up.validate`](/up.validate) function.
|
5387
|
+
|
5388
|
+
\#\#\#\# Example
|
5389
|
+
|
5390
|
+
Let's look at a standard registration form that asks for an e-mail and password:
|
5391
|
+
|
5392
|
+
<form action="/users">
|
5393
|
+
|
5394
|
+
<label>
|
5395
|
+
E-mail: <input type="text" name="email" />
|
5396
|
+
</label>
|
5397
|
+
|
5398
|
+
<label>
|
5399
|
+
Password: <input type="password" name="password" />
|
5400
|
+
</label>
|
5401
|
+
|
5402
|
+
<button type="submit">Register</button>
|
5403
|
+
|
5404
|
+
</form>
|
5405
|
+
|
5406
|
+
When the user changes the `email` field, we want to validate that
|
5407
|
+
the e-mail address is valid and still available. Also we want to
|
5408
|
+
change the `password` field for the minimum required password length.
|
5409
|
+
We can do this by giving both fields an `up-validate` attribute:
|
5410
|
+
|
5411
|
+
<form action="/users">
|
5412
|
+
|
5413
|
+
<label>
|
5414
|
+
E-mail: <input type="text" name="email" up-validate />
|
5415
|
+
</label>
|
5416
|
+
|
5417
|
+
<label>
|
5418
|
+
Password: <input type="password" name="password" up-validate />
|
5419
|
+
</label>
|
5420
|
+
|
5421
|
+
<button type="submit">Register</button>
|
5422
|
+
|
5423
|
+
</form>
|
5424
|
+
|
5425
|
+
Whenever a field with `up-validate` changes, the form is POSTed to
|
5426
|
+
`/users` with an additional `X-Up-Validate` HTTP header.
|
5427
|
+
Upon seeing this header, the server is expected to validate (but not save)
|
5428
|
+
the form submission and render a new copy of the form with validation errors.
|
5429
|
+
|
5430
|
+
In Ruby on Rails the processing action should behave like this:
|
5431
|
+
|
5432
|
+
class UsersController < ApplicationController
|
5433
|
+
|
5434
|
+
* This action handles POST /users
|
5435
|
+
def create
|
5436
|
+
user_params = params[:user].permit(:email, :password)
|
5437
|
+
@user = User.new(user_params)
|
5438
|
+
if request.headers['X-Up-Validate']
|
5439
|
+
@user.valid? # run validations, but don't save to the database
|
5440
|
+
render 'form' # render form with error messages
|
5441
|
+
elsif @user.save?
|
5442
|
+
sign_in @user
|
5443
|
+
else
|
5444
|
+
render 'form', status: :bad_request
|
5445
|
+
end
|
5446
|
+
end
|
5447
|
+
|
5448
|
+
end
|
5449
|
+
|
5450
|
+
Note that if you're using the `upjs-rails` gem you can simply say `up.validate?`
|
5451
|
+
instead of manually checking for `request.headers['X-Up-Validate']`.
|
5452
|
+
|
5453
|
+
The server now renders an updated copy of the form with eventual validation errors:
|
5454
|
+
|
5455
|
+
<form action="/users">
|
5456
|
+
|
5457
|
+
<label class="has-error">
|
5458
|
+
E-mail: <input type="text" name="email" value="foo@bar.com" />
|
5459
|
+
Has already been taken!
|
5460
|
+
</label>
|
5461
|
+
|
5462
|
+
<button type="submit">Register</button>
|
5463
|
+
|
5464
|
+
</form>
|
5465
|
+
|
5466
|
+
The `<label>` around the e-mail field is now updated to have the `has-error`
|
5467
|
+
class and display the validation message.
|
5468
|
+
|
5469
|
+
\#\#\#\# How validation results are displayed
|
5470
|
+
|
5471
|
+
Although the server will usually respond to a validation with a complete,
|
5472
|
+
fresh copy of the form, Up.js will by default not update the entire form.
|
5473
|
+
This is done in order to preserve volatile state such as the scroll position
|
5474
|
+
of `<textarea>` elements.
|
5475
|
+
|
5476
|
+
By default Up.js looks for a `<fieldset>`, `<label>` or `<form>`
|
5477
|
+
around the validating input field, or any element with an
|
5478
|
+
`up-fieldset` attribute.
|
5479
|
+
With the Bootstrap bindings, Up.js will also look
|
5480
|
+
for a container with the `form-group` class.
|
5481
|
+
|
5482
|
+
You can change this default behavior by setting `up.config.validateTargets`:
|
5483
|
+
|
5484
|
+
// Always update the entire form containing the current field ("&")
|
5485
|
+
up.config.validateTargets = ['form &']
|
5486
|
+
|
5487
|
+
You can also individually override what to update by setting the `up-validate`
|
5488
|
+
attribute to a CSS selector:
|
5489
|
+
|
5490
|
+
<input type="text" name="email" up-validate=".email-errors">
|
5491
|
+
<span class="email-errors"></span>
|
5492
|
+
|
5493
|
+
|
5494
|
+
\#\#\#\# Updating dependent fields
|
5495
|
+
|
5496
|
+
The `[up-validate]` behavior is also a great way to partially update a form
|
5497
|
+
when one fields depends on the value of another field.
|
5498
|
+
|
5499
|
+
Let's say you have a form with one `<select>` to pick a department (sales, engineering, ...)
|
5500
|
+
and another `<select>` to pick an employeee from the selected department:
|
5501
|
+
|
5502
|
+
<form action="/contracts">
|
5503
|
+
<select name="department">...</select> <!-- options for all departments -->
|
5504
|
+
<select name="employeed">...</select> <!-- options for employees of selected department -->
|
5505
|
+
</form>
|
5506
|
+
|
5507
|
+
The list of employees needs to be updated as the appartment changes:
|
5508
|
+
|
5509
|
+
<form action="/contracts">
|
5510
|
+
<select name="department" up-validate="[name=employee]">...</select>
|
5511
|
+
<select name="employee">...</select>
|
5512
|
+
</form>
|
5513
|
+
|
5514
|
+
In order to update the `department` field in addition to the `employee` field, you could say
|
5515
|
+
`up-validate="&, [name=employee]"`, or simply `up-validate="form"` to update the entire form.
|
5516
|
+
|
5517
|
+
@selector [up-validate]
|
5518
|
+
@param {String} up-validate
|
5519
|
+
The CSS selector to update with the server response.
|
5520
|
+
|
5521
|
+
This defaults to a fieldset or form group around the validating field.
|
5522
|
+
*/
|
5523
|
+
up.on('change', '[up-validate]', function(event, $field) {
|
5524
|
+
return validate($field);
|
5525
|
+
});
|
5526
|
+
|
5089
5527
|
/**
|
5090
5528
|
Observes this form field and runs the given script
|
5091
5529
|
when its value changes. This is useful for observing text fields
|
5092
5530
|
while the user is typing.
|
5093
5531
|
|
5532
|
+
The programmatic variant of this is the [`up.observe`](/up.observe) function.
|
5533
|
+
|
5534
|
+
\#\#\#\# Example
|
5535
|
+
|
5094
5536
|
For instance, the following would submit the form whenever the
|
5095
5537
|
text field value changes:
|
5096
5538
|
|
@@ -5098,7 +5540,7 @@ We need to work on this page:
|
|
5098
5540
|
<input type="query" up-observe="up.form.submit(this)">
|
5099
5541
|
</form>
|
5100
5542
|
|
5101
|
-
The script given
|
5543
|
+
The script given to `up-observe` runs with the following context:
|
5102
5544
|
|
5103
5545
|
| Name | Type | Description |
|
5104
5546
|
| -------- | --------- | ------------------------------------- |
|
@@ -5106,18 +5548,18 @@ We need to work on this page:
|
|
5106
5548
|
| `this` | `Element` | The form field |
|
5107
5549
|
| `$field` | `jQuery` | The form field as a jQuery collection |
|
5108
5550
|
|
5109
|
-
|
5110
|
-
|
5111
|
-
@selector input[up-observe]
|
5112
|
-
The code to run when the field's value changes.
|
5551
|
+
@selector [up-observe]
|
5113
5552
|
@param {String} up-observe
|
5553
|
+
The code to run when the field's value changes.
|
5114
5554
|
*/
|
5115
5555
|
up.compiler('[up-observe]', function($field) {
|
5116
5556
|
return observe($field);
|
5117
5557
|
});
|
5558
|
+
up.on('up:framework:reset', reset);
|
5118
5559
|
return {
|
5119
5560
|
submit: submit,
|
5120
|
-
observe: observe
|
5561
|
+
observe: observe,
|
5562
|
+
validate: validate
|
5121
5563
|
};
|
5122
5564
|
})(jQuery);
|
5123
5565
|
|
@@ -5125,6 +5567,8 @@ We need to work on this page:
|
|
5125
5567
|
|
5126
5568
|
up.observe = up.form.observe;
|
5127
5569
|
|
5570
|
+
up.validate = up.form.validate;
|
5571
|
+
|
5128
5572
|
}).call(this);
|
5129
5573
|
|
5130
5574
|
/**
|
@@ -5969,6 +6413,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
|
|
5969
6413
|
});
|
5970
6414
|
up.on('up:framework:reset', reset);
|
5971
6415
|
return {
|
6416
|
+
knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
|
5972
6417
|
visit: visit,
|
5973
6418
|
follow: follow,
|
5974
6419
|
open: function() {
|