upjs-rails 0.12.5 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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() {
|