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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +23 -0
  3. data/CHANGELOG.md +20 -0
  4. data/design/up-validate.js.coffee +284 -0
  5. data/dist/up-bootstrap.js +4 -0
  6. data/dist/up-bootstrap.min.js +1 -1
  7. data/dist/up.js +547 -102
  8. data/dist/up.min.js +2 -2
  9. data/lib/assets/javascripts/up/browser.js.coffee +3 -2
  10. data/lib/assets/javascripts/up/flow.js.coffee +95 -17
  11. data/lib/assets/javascripts/up/form.js.coffee +327 -34
  12. data/lib/assets/javascripts/up/history.js.coffee +1 -1
  13. data/lib/assets/javascripts/up/layout.js.coffee +4 -4
  14. data/lib/assets/javascripts/up/link.js.coffee +5 -2
  15. data/lib/assets/javascripts/up/modal.js.coffee +1 -0
  16. data/lib/assets/javascripts/up/proxy.js.coffee +27 -12
  17. data/lib/assets/javascripts/up/syntax.js.coffee +39 -20
  18. data/lib/assets/javascripts/up/util.js.coffee +29 -12
  19. data/lib/assets/javascripts/up-bootstrap/form-ext.js.coffee +1 -0
  20. data/lib/upjs/rails/engine.rb +1 -1
  21. data/lib/upjs/rails/inspector.rb +63 -0
  22. data/lib/upjs/rails/inspector_accessor.rb +28 -0
  23. data/lib/upjs/rails/request_echo_headers.rb +7 -0
  24. data/lib/upjs/rails/request_method_cookie.rb +12 -4
  25. data/lib/upjs/rails/version.rb +5 -1
  26. data/lib/upjs-rails.rb +7 -5
  27. data/spec_app/.rspec +2 -0
  28. data/spec_app/Gemfile +0 -3
  29. data/spec_app/Gemfile.lock +43 -44
  30. data/spec_app/app/assets/stylesheets/application.css +1 -1
  31. data/spec_app/app/controllers/test_controller.rb +23 -0
  32. data/spec_app/config/routes.rb +2 -0
  33. data/spec_app/spec/controllers/test_controller_spec.rb +67 -0
  34. data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +8 -0
  35. data/spec_app/spec/javascripts/helpers/last_request.js.coffee +18 -0
  36. data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +1 -0
  37. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +93 -43
  38. data/spec_app/spec/javascripts/up/form_spec.js.coffee +80 -18
  39. data/spec_app/spec/javascripts/up/history_spec.js.coffee +1 -5
  40. data/spec_app/spec/javascripts/up/link_spec.js.coffee +18 -17
  41. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +32 -37
  42. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +7 -26
  43. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -7
  44. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +26 -25
  45. data/spec_app/spec/javascripts/up/util_spec.js.coffee +23 -0
  46. data/spec_app/spec/spec_helper.rb +62 -0
  47. metadata +12 -3
  48. 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
- "X-Up-Selector": request.selector
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($element) {
265
- var classString, classes, id, j, klass, len, selector;
266
- debug("Creating selector from element %o", $element);
267
- classes = (classString = $element.attr("class")) ? classString.split(" ") : [];
268
- id = $element.attr("id");
269
- selector = $element.prop("tagName").toLowerCase();
270
- if (id) {
271
- selector += "#" + id;
272
- }
273
- for (j = 0, len = classes.length; j < len; j++) {
274
- klass = classes[j];
275
- selector += "." + klass;
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
- leaving you with a classic server-side application.
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
- Custom elements
1665
- ===============
1666
-
1679
+ Enhancing elements
1680
+ ==================
1681
+
1667
1682
  Up.js keeps a persistent Javascript environment during page transitions.
1668
- To prevent memory leaks it is important to cleanly set up and tear down
1669
- event handlers and custom elements.
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
- \#\#\# Incomplete documentation!
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
- We need to work on this page:
1691
+ <a href="river.png" class="lightbox">River</a>
1692
+ <a href="ocean.png" class="lightbox">Ocean</a>
1674
1693
 
1675
- - Better class-level introduction for this module
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 [Angular directives](https://docs.angularjs.org/guide/directive).
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 `[rel=lightbox]` attribute:
1743
+ do this for all links with an `lightbox` class:
1710
1744
 
1711
- <a href="river.png" rel="lightbox">River</a>
1712
- <a href="ocean.png" rel="lightbox">Ocean</a>
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[rel=lightbox]', function($element) {
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
- Sends a notification that the given element has been inserted
1962
- into the DOM. This causes Up.js to compile the fragment (apply
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 plugin code that
1968
- is not aware of Up.js:
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
- // Add an element with naked jQuery, without going through Upjs:
1971
- $element = $('<div>...</div>').appendTo(document.body);
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<String>} [config.popTargets=['body']]
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<String>} [config.viewports]
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<String>} [config.fixedTop]
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<String>} [config.fixedBottom]
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<String>} [config.anchoredRight]
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 = u.presence(selectorOrElement) ? selectorOrElement : u.createSelectorFromElement($(selectorOrElement));
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} selector
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(selector, html, options) {
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 = htmlElement.querySelector(selector)) {
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: selectorParts[1],
3111
- pseudoClass: selectorParts[2],
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} selector
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(selector) {
3255
+ first = function(selectorOrElement) {
3144
3256
  var $element, $match, element, elements, j, len;
3145
- elements = $(selector).get();
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` [event](/up.bus).
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 = cache.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` [event](/up.bus) if it
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` [event](/up.bus) if it
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:busy
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 [`restore scroll positions`](/up.restoreScroll)
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 and controls
4790
- ==================
4936
+ Forms
4937
+ =====
4791
4938
 
4792
- Up.js comes with functionality to submit forms without
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
- Submits a form using the Up.js flow:
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.new_user')
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
- For instance, the following would submit the form whenever the
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
- Submits the form through AJAX, searches the response for the selector
5049
- given in `up-target` and [replaces](/up.replace) the selector content in the current page:
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 with `up-observe` runs with the following context:
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
- See up.observe.
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() {