unpoly-rails 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eff167c9b219c6ff6e513dcddaad4f69a000a2d1
4
- data.tar.gz: e60d7f41c8d7b36d9156dddfe691f78927469341
3
+ metadata.gz: 513b5201d93ee8e087b990e708f6c0c3fae07d59
4
+ data.tar.gz: 0034f1a7a0e17e1f43d2a33d9783a831fc338880
5
5
  SHA512:
6
- metadata.gz: cff124d22a03502c85c1cf487c18abf1530771216eea237c183c37a0cf7b4f42b0fca3032b36c0af179765d8ea8054512e41f61c71d171483f61ea3acde85c6a
7
- data.tar.gz: 9174013d3562bd2bad0507907b17b831c707a1f87a2dcf2832858cc8706e9d736faaa02157e9a1ea54c37a717b0502402b73b62e71c8ff165fb3764e926b44a1
6
+ metadata.gz: 8a75fc96dee7eac045e62627ea82aec8509106d45e29a3d884fad95257be227aecaa42ed6983a6142fc45aa9b633f017b6ffd77bd73111a5b434c5ea00d6d676
7
+ data.tar.gz: 1815f021f9da6e10e087a5c68eacf7c7f841787af52175afa88ad599b29ef3c6de45762a9a6572c3f773ac2b097e4e9566cf757198276f85d5846cb1064602fc
data/CHANGELOG.md CHANGED
@@ -6,6 +6,31 @@ All notable changes to this project will be documented in this file.
6
6
  This project mostly adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
 
9
+ 0.34.0
10
+ ------
11
+
12
+ ### Compatible changes
13
+
14
+ - During the initial page load Unpoly now [reveals](/up.reveal) an element matching the
15
+ `#hash` in the current URL.
16
+
17
+ Other than the default behavior found in browsers, `up.revealHash` works with
18
+ [multiple viewports](/up-viewport) and honors [fixed elements](/up-fixed-top) obstructing the user's
19
+ view of the viewport.
20
+ - New experimental function [`up.layout.revealHash()`](/up.layout.revealHash).
21
+ - The optional server protocol is now [documented](/up.protocol).
22
+ The protocol is already implemented by the [`unpoly-rails`](https://rubygems.org/gems/unpoly-rails) Ruby gem.
23
+ - New experimental property [`up.protocol.config`](/up.protocol.config)
24
+ - [`up.browser.isSupported()`](/up.browser.isSupported) has been promoted from experimental to stable API
25
+
26
+
27
+ ### Breaking changes
28
+
29
+ - `up.proxy.config.wrapMethodParam` is now [`up.protocol.config.methodParam`](/up.protocol.config#config.methodParam).
30
+ - The event [`up:history:restored`](/up:history:restored) is no longer emitted when a history state
31
+ was not created by Unpoly.
32
+
33
+
9
34
  0.33.0
10
35
  ------
11
36
 
data/README_RAILS.md CHANGED
@@ -11,11 +11,11 @@ The methods documented below are available in all controllers, views and helpers
11
11
 
12
12
  ### Detecting a fragment update
13
13
 
14
- To test whether the current request is a [fragment update](http://unpoly.com/up.replace):
14
+ To test whether the current request is a [fragment update](http://unpoly.com/up.link):
15
15
 
16
16
  up?
17
17
 
18
- To retrieve the CSS selector that is being [updated](http://unpoly.com/up.replace):
18
+ To retrieve the CSS selector that is being [updated](http://unpoly.com/up.link):
19
19
 
20
20
  up.target
21
21
 
@@ -70,7 +70,7 @@ The Unpoly frontend [requires these headers to detect redirects](http://unpoly.c
70
70
 
71
71
  ### Automatic method detection for initial page load
72
72
 
73
- `unpoly-rails` sets an `_up_request_method` cookie that Unpoly needs to detect the request method for the initial page load.
73
+ `unpoly-rails` sets an `_up_method` cookie that Unpoly needs to detect the request method for the initial page load.
74
74
 
75
75
  If the initial page was loaded with a non-`GET` HTTP method, Unpoly will fall back to full page loads for all actions that require `pushState`.
76
76
 
data/dist/unpoly.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  (function() {
7
7
  this.up = {
8
- version: "0.33.0",
8
+ version: "0.34.0",
9
9
  renamedModule: function(oldName, newName) {
10
10
  return typeof Object.defineProperty === "function" ? Object.defineProperty(up, oldName, {
11
11
  get: function() {
@@ -41,7 +41,7 @@ that might save you from loading something like [Underscore.js](http://underscor
41
41
  @function up.util.noop
42
42
  @experimental
43
43
  */
44
- var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, DivertibleChain, ESCAPE_HTML_ENTITY_MAP, all, any, appendRequestData, assign, assignPolyfill, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detachWith, detect, documentHasVerticalScrollbar, each, escapeHtml, escapePressed, evalOption, except, extractOptions, fail, findWithSelf, finishCssAnimate, fixedToAbsolute, flatten, forceCompositing, forceRepaint, horizontalScreenHalf, identity, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFixed, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isResolvedPromise, isStandardPort, isString, isTruthy, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, margins, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, opacity, openConfig, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, previewable, promiseTimer, reject, rejectedPromise, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, sequence, setMissingAttrs, setTimer, submittedValue, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, whenReady;
44
+ var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, DivertibleChain, ESCAPE_HTML_ENTITY_MAP, all, any, appendRequestData, assign, assignPolyfill, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detachWith, detect, documentHasVerticalScrollbar, each, escapeHtml, escapePressed, evalOption, except, extractOptions, fail, findWithSelf, finishCssAnimate, fixedToAbsolute, flatten, forceCompositing, forceRepaint, horizontalScreenHalf, identity, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFixed, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isResolvedPromise, isStandardPort, isString, isTruthy, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, map, margins, measure, memoize, merge, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, opacity, openConfig, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, previewable, promiseTimer, reject, rejectedPromise, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, sequence, setMissingAttrs, setTimer, submittedValue, temporaryCss, times, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, whenReady;
45
45
  noop = $.noop;
46
46
 
47
47
  /**
@@ -1366,30 +1366,6 @@ that might save you from loading something like [Underscore.js](http://underscor
1366
1366
  }
1367
1367
  };
1368
1368
 
1369
- /**
1370
- @function up.util.locationFromXhr
1371
- @internal
1372
- */
1373
- locationFromXhr = function(xhr) {
1374
- return xhr.getResponseHeader('X-Up-Location');
1375
- };
1376
-
1377
- /**
1378
- @function up.util.titleFromXhr
1379
- @internal
1380
- */
1381
- titleFromXhr = function(xhr) {
1382
- return xhr.getResponseHeader('X-Up-Title');
1383
- };
1384
-
1385
- /**
1386
- @function up.util.methodFromXhr
1387
- @internal
1388
- */
1389
- methodFromXhr = function(xhr) {
1390
- return xhr.getResponseHeader('X-Up-Method');
1391
- };
1392
-
1393
1369
  /**
1394
1370
  Returns a copy of the given object that only contains
1395
1371
  the given properties.
@@ -2463,9 +2439,6 @@ that might save you from loading something like [Underscore.js](http://underscor
2463
2439
  contains: contains,
2464
2440
  toArray: toArray,
2465
2441
  castedAttr: castedAttr,
2466
- locationFromXhr: locationFromXhr,
2467
- titleFromXhr: titleFromXhr,
2468
- methodFromXhr: methodFromXhr,
2469
2442
  clientSize: clientSize,
2470
2443
  only: only,
2471
2444
  except: except,
@@ -2513,6 +2486,244 @@ that might save you from loading something like [Underscore.js](http://underscor
2513
2486
 
2514
2487
  }).call(this);
2515
2488
 
2489
+ /**
2490
+ Server protocol
2491
+ ===============
2492
+
2493
+ You rarely need to change server-side code
2494
+ in order to use Unpoly. There is no need to provide a JSON API, or add
2495
+ extra routes for AJAX requests. The server simply renders a series
2496
+ of full HTML pages, just like it would without Unpoly.
2497
+
2498
+ That said, there is an **optional** protocol your server can use to
2499
+ exchange additional information when Unpoly is [updating fragments](/up.link).
2500
+
2501
+ While the protocol can help you optimize performance and handle some
2502
+ edge cases, implementing it is entirely optional. For instance,
2503
+ `unpoly.com` itself is a static site that uses Unpoly on the frontend
2504
+ and doesn't even have a server component.
2505
+
2506
+ If you have [installed Unpoly as a Rails gem](/install/rails), the protocol
2507
+ is already implemented and you will get some
2508
+ [Ruby bindings](https://github.com/unpoly/unpoly/blob/master/README_RAILS.md)
2509
+ in your controllers and views. If your server-side app uses another language
2510
+ or framework, you should be able to implement the protocol in a very short time.
2511
+
2512
+
2513
+ \#\#\# Redirect detection
2514
+
2515
+ Unpoly requires an additional response header to detect redirects, which are
2516
+ otherwise undetectable for any AJAX client.
2517
+
2518
+ After the form's action performs a redirect, the next response should include the new
2519
+ URL in this HTTP header:
2520
+
2521
+ ```http
2522
+ X-Up-Location: /current-url
2523
+ ```
2524
+
2525
+ The **simplest implementation** is to set this header for every request.
2526
+
2527
+
2528
+ \#\#\# Optimizing responses
2529
+
2530
+ When [updating a fragment](http://unpoly.com/up.link), Unpoly will send
2531
+ an additional HTTP header containing the CSS selector that is being replaced:
2532
+
2533
+ ```http
2534
+ X-Up-Target: .user-list
2535
+ ```
2536
+
2537
+ Server-side code is free to **optimize its response** by only returning HTML
2538
+ that matches the selector. For example, you might prefer to not render an
2539
+ expensive sidebar if the sidebar is not targeted.
2540
+
2541
+
2542
+ \#\#\# Pushing a document title to the client
2543
+
2544
+ When [updating a fragment](http://unpoly.com/up.link), Unpoly will by default
2545
+ extract the `<title>` from the server response and update the document title accordingly.
2546
+
2547
+ The server can also force Unpoly to set a document title by passing a HTTP header:
2548
+
2549
+ ```http
2550
+ X-Up-Title: My server-pushed title
2551
+ ```
2552
+
2553
+ This is useful when you [optimize your response](#optimizing-responses) and not render
2554
+ the application layout unless it is targeted. Since your optimized response
2555
+ no longer includes a `<title>`, you can instead use the HTTP header to pass the document title.
2556
+
2557
+
2558
+ \#\#\# Signaling failed form submissions
2559
+
2560
+ When [submitting a form via AJAX](http://unpoly.com/form-up-target)
2561
+ Unpoly needs to know whether the form submission has failed (to update the form with
2562
+ validation errors) or succeeded (to update the `up-target` selector).
2563
+
2564
+ For Unpoly to be able to detect a failed form submission, the response must be
2565
+ return a non-200 HTTP status code. We recommend to use either
2566
+ 400 (bad request) or 422 (unprocessable entity).
2567
+
2568
+ To do so in [Ruby on Rails](http://rubyonrails.org/), pass a [`:status` option to `render`](http://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option):
2569
+
2570
+ class UsersController < ApplicationController
2571
+
2572
+ def create
2573
+ user_params = params[:user].permit(:email, :password)
2574
+ @user = User.new(user_params)
2575
+ if @user.save?
2576
+ sign_in @user
2577
+ else
2578
+ render 'form', status: :bad_request
2579
+ end
2580
+ end
2581
+
2582
+ end
2583
+
2584
+
2585
+ \#\#\# Detecting live form validations
2586
+
2587
+ When [validating a form](http://unpoly.com/up-validate), Unpoly will
2588
+ send an additional HTTP header containing a CSS selector for the form that is
2589
+ being updated:
2590
+
2591
+ ```http
2592
+ X-Up-Validate: .user-form
2593
+ ```
2594
+
2595
+ When detecting a validation request, the server is expected to **validate (but not save)**
2596
+ the form submission and render a new copy of the form with validation errors.
2597
+
2598
+ Below you will an example for a writing route that is aware of Unpoly's live form
2599
+ validations. The code is for [Ruby on Rails](http://rubyonrails.org/),
2600
+ but you can adapt it for other languages:
2601
+
2602
+ class UsersController < ApplicationController
2603
+
2604
+ def create
2605
+ user_params = params[:user].permit(:email, :password)
2606
+ @user = User.new(user_params)
2607
+ if request.headers['X-Up-Validate']
2608
+ @user.valid? # run validations, but don't save to the database
2609
+ render 'form' # render form with error messages
2610
+ elsif @user.save?
2611
+ sign_in @user
2612
+ else
2613
+ render 'form', status: :bad_request
2614
+ end
2615
+ end
2616
+
2617
+ end
2618
+
2619
+
2620
+ \#\#\# Signaling the initial request method
2621
+
2622
+ This is a edge case you might or might not care about:
2623
+ If the initial page was loaded with a non-`GET` HTTP method, Unpoly prefers to make a full
2624
+ page load when you try to update a fragment. Once a page was loaded with a `GET` method,
2625
+ Unpoly will restore its standard behavior.
2626
+
2627
+ The reason for this is that some browsers remember the method of the initial page load and don't let
2628
+ the application change it, even with `pushState`. Thus, when the user reloads the page much later,
2629
+ an affected browser might request a `POST`, `PUT`, etc. instead of the correct method.
2630
+
2631
+ In order to allow Unpoly to detect the HTTP method of the initial page load,
2632
+ the server must set a cookie:
2633
+
2634
+ ```http
2635
+ Set-Cookie: _up_method=POST
2636
+ ```
2637
+
2638
+ When Unpoly boots, it will look for this cookie and configure its behavior accordingly.
2639
+ The cookie is then deleted in order to not affect following requests.
2640
+
2641
+ The **simplest implementation** is to set this cookie for every request that is neither
2642
+ `GET` nor contains an [`X-Up-Target` header](/#optimizing-responses). For all other requests
2643
+ an existing cookie should be deleted.
2644
+
2645
+
2646
+ @class up.protocol
2647
+ */
2648
+
2649
+ (function() {
2650
+ up.protocol = (function($) {
2651
+ var config, initialRequestMethod, locationFromXhr, methodFromXhr, titleFromXhr, u;
2652
+ u = up.util;
2653
+
2654
+ /**
2655
+ @function up.protocol.locationFromXhr
2656
+ @internal
2657
+ */
2658
+ locationFromXhr = function(xhr) {
2659
+ return xhr.getResponseHeader(config.locationHeader);
2660
+ };
2661
+
2662
+ /**
2663
+ @function up.protocol.titleFromXhr
2664
+ @internal
2665
+ */
2666
+ titleFromXhr = function(xhr) {
2667
+ return xhr.getResponseHeader(config.titleHeader);
2668
+ };
2669
+
2670
+ /**
2671
+ @function up.protocol.methodFromXhr
2672
+ @internal
2673
+ */
2674
+ methodFromXhr = function(xhr) {
2675
+ return xhr.getResponseHeader(config.methodHeader);
2676
+ };
2677
+
2678
+ /**
2679
+ Server-side companion libraries like unpoly-rails set this cookie so we
2680
+ have a way to detect the request method of the initial page load.
2681
+ There is no JavaScript API for this.
2682
+
2683
+ @function up.protocol.initialRequestMethod
2684
+ @internal
2685
+ */
2686
+ initialRequestMethod = u.memoize(function() {
2687
+ var methodFromServer;
2688
+ methodFromServer = up.browser.popCookie(config.methodCookie);
2689
+ return (methodFromServer || 'get').toLowerCase();
2690
+ });
2691
+
2692
+ /**
2693
+ Configures strings used in the optional [server protocol](/up.protocol).
2694
+
2695
+ @property up.protocol.config
2696
+ @param [config.targetHeader='X-Up-Target']
2697
+ @param [config.locationHeader='X-Up-Location']
2698
+ @param [config.titleHeader='X-Up-Title']
2699
+ @param [config.validateHeader='X-Up-Validate']
2700
+ @param [config.methodHeader='X-Up-Method']
2701
+ @param [config.methodCookie='_up_method']
2702
+ @param [config.methodParam='_method']
2703
+ The name of the POST parameter when [wrapping HTTP methods](/up.form.config#config.wrapMethods)
2704
+ in a `POST` request.
2705
+ @experimental
2706
+ */
2707
+ config = u.config({
2708
+ targetHeader: 'X-Up-Target',
2709
+ locationHeader: 'X-Up-Location',
2710
+ validateHeader: 'X-Up-Validate',
2711
+ titleHeader: 'X-Up-Title',
2712
+ methodHeader: 'X-Up-Method',
2713
+ methodCookie: '_up_method',
2714
+ methodParam: '_method'
2715
+ });
2716
+ return {
2717
+ config: config,
2718
+ locationFromXhr: locationFromXhr,
2719
+ titleFromXhr: titleFromXhr,
2720
+ methodFromXhr: methodFromXhr,
2721
+ initialRequestMethod: initialRequestMethod
2722
+ };
2723
+ })(jQuery);
2724
+
2725
+ }).call(this);
2726
+
2516
2727
  /**
2517
2728
  Browser support
2518
2729
  ===============
@@ -2536,7 +2747,7 @@ IE 8
2536
2747
  var slice = [].slice;
2537
2748
 
2538
2749
  up.browser = (function($) {
2539
- var CONSOLE_PLACEHOLDERS, canCssTransition, canFormData, canInputEvent, canLogSubstitution, canPushState, initialRequestMethod, installPolyfills, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, sessionStorage, setLocationHref, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed;
2750
+ var CONSOLE_PLACEHOLDERS, canCssTransition, canFormData, canInputEvent, canLogSubstitution, canPushState, hash, installPolyfills, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, sessionStorage, setLocationHref, sprintf, sprintfWithFormattedArgs, stringifyArg, submitForm, u, url, whenConfirmed;
2540
2751
  u = up.util;
2541
2752
 
2542
2753
  /**
@@ -2567,7 +2778,7 @@ IE 8
2567
2778
  return $field.appendTo($form);
2568
2779
  };
2569
2780
  addField({
2570
- name: up.proxy.config.wrapMethodParam,
2781
+ name: up.protocol.config.methodParam,
2571
2782
  value: method
2572
2783
  });
2573
2784
  if (csrfField = up.rails.csrfField()) {
@@ -2719,7 +2930,7 @@ IE 8
2719
2930
  @experimental
2720
2931
  */
2721
2932
  canPushState = u.memoize(function() {
2722
- return u.isDefined(history.pushState) && initialRequestMethod() === 'get';
2933
+ return u.isDefined(history.pushState) && up.protocol.initialRequestMethod() === 'get';
2723
2934
  });
2724
2935
 
2725
2936
  /**
@@ -2785,6 +2996,14 @@ IE 8
2785
2996
  minor = parseInt(parts[1]);
2786
2997
  return major >= 2 || (major === 1 && minor >= 9);
2787
2998
  });
2999
+
3000
+ /**
3001
+ Returns and deletes a cookie with the given name
3002
+ Inspired by Turbolinks: https://github.com/rails/turbolinks/blob/83d4b3d2c52a681f07900c28adb28bc8da604733/lib/assets/javascripts/turbolinks.coffee#L292
3003
+
3004
+ @function up.browser.popCookie
3005
+ @internal
3006
+ */
2788
3007
  popCookie = function(name) {
2789
3008
  var ref, value;
2790
3009
  value = (ref = document.cookie.match(new RegExp(name + "=(\\w+)"))) != null ? ref[1] : void 0;
@@ -2808,25 +3027,25 @@ IE 8
2808
3027
  return u.unresolvablePromise();
2809
3028
  }
2810
3029
  };
2811
- initialRequestMethod = u.memoize(function() {
2812
- return (popCookie('_up_request_method') || 'get').toLowerCase();
2813
- });
2814
3030
 
2815
3031
  /**
2816
3032
  Returns whether Unpoly supports the current browser.
2817
3033
 
2818
- This also returns `true` if Unpoly only support some features, but falls back
2819
- gracefully for other features. E.g. IE9 is almost fully supported, but due to
2820
- its lack of [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
2821
- Unpoly falls back to a full page load when asked to manipulate history.
2822
-
2823
- Currently Unpoly supports IE9 with jQuery 1.9+.
2824
- On older browsers Unpoly will prevent itself from [booting](/up.boot)
3034
+ If this returns `false` Unpoly will prevent itself from [booting](/up.boot)
2825
3035
  and ignores all registered [event handlers](/up.on) and [compilers](/up.compiler).
2826
3036
  This leaves you with a classic server-side application.
3037
+ This is usually a better fallback than loading incompatible Javascript and causing
3038
+ many errors on load.
3039
+
3040
+ \#\#\# Graceful degradation
3041
+
3042
+ This function also returns `true` if Unpoly only support some features, but can degrade
3043
+ gracefully for other features. E.g. Internet Explorer 9 is almost fully supported, but due to
3044
+ its lack of [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
3045
+ Unpoly falls back to a full page load when asked to manipulate history.
2827
3046
 
2828
3047
  @function up.browser.isSupported
2829
- @experimental
3048
+ @stable
2830
3049
  */
2831
3050
  isSupported = function() {
2832
3051
  return (!isIE8OrWorse()) && isRecentJQuery();
@@ -2864,6 +3083,23 @@ IE 8
2864
3083
  removeItem: u.noop
2865
3084
  };
2866
3085
  });
3086
+
3087
+ /**
3088
+ Returns `'foo'` if the hash is `'#foo'`.
3089
+
3090
+ Returns undefined if the hash is `'#'`, `''` or `undefined`.
3091
+
3092
+ @function up.browser.hash
3093
+ @internal
3094
+ */
3095
+ hash = function(value) {
3096
+ value || (value = location.hash);
3097
+ value || (value = '');
3098
+ if (value[0] === '#') {
3099
+ value = value.substr(1);
3100
+ }
3101
+ return u.presence(value);
3102
+ };
2867
3103
  return {
2868
3104
  knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
2869
3105
  url: url,
@@ -2879,7 +3115,9 @@ IE 8
2879
3115
  puts: puts,
2880
3116
  sprintf: sprintf,
2881
3117
  sprintfWithFormattedArgs: sprintfWithFormattedArgs,
2882
- sessionStorage: sessionStorage
3118
+ sessionStorage: sessionStorage,
3119
+ popCookie: popCookie,
3120
+ hash: hash
2883
3121
  };
2884
3122
  })(jQuery);
2885
3123
 
@@ -2898,8 +3136,10 @@ Most Unpoly interactions emit DOM events that are prefixed with `up:`.
2898
3136
  Events often have both present ([`up:modal:open`](/up:modal:open))
2899
3137
  and past forms ([`up:modal:opened`](/up:modal:opened)).
2900
3138
 
2901
- You can usually prevent an action by listening to the present form
2902
- and call `preventDefault()` on the `event` object:
3139
+
3140
+ \#\#\# Preventing events
3141
+
3142
+ You can prevent most present form events by calling `preventDefault()`:
2903
3143
 
2904
3144
  $(document).on('up:modal:open', function(event) {
2905
3145
  if (event.url == '/evil') {
@@ -2909,8 +3149,7 @@ and call `preventDefault()` on the `event` object:
2909
3149
  });
2910
3150
 
2911
3151
 
2912
- A better way to bind event listeners
2913
- ------------------------------------
3152
+ \#\#\# A better way to bind event listeners
2914
3153
 
2915
3154
  Instead of using jQuery to bind an event handler to `document`, you can also
2916
3155
  use the more convenient [`up.on()`](/up.on):
@@ -3365,8 +3604,6 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
3365
3604
  Unpoly will not boot if the current browser is [not supported](/up.browser.isSupported).
3366
3605
  This leaves you with a classic server-side application on legacy browsers.
3367
3606
 
3368
- Emits the [`up:framework:boot`](/up:framework:boot) event.
3369
-
3370
3607
  @function up.boot
3371
3608
  @internal
3372
3609
  */
@@ -4129,8 +4366,8 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4129
4366
  */
4130
4367
 
4131
4368
  /**
4132
- If an element annotated with [`up-data`] is inserted into the DOM,
4133
- Up will parse the JSON and pass the resulting object to any matching
4369
+ If an element with an `up-data` attribute enters the DOM,
4370
+ Unpoly will parse the JSON and pass the resulting object to any matching
4134
4371
  [`up.compiler()`](/up.compiler) handlers.
4135
4372
 
4136
4373
  For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
@@ -4232,13 +4469,12 @@ or when a matching fragment is [inserted via AJAX](/up.link) later.
4232
4469
  Browser history
4233
4470
  ===============
4234
4471
 
4235
- \#\#\# Incomplete documentation!
4236
-
4237
- We need to work on this page:
4472
+ In an Unpoly app, every page has an URL.
4238
4473
 
4239
- - Explain how the other modules manipulate history
4240
- - Decide whether we want to expose these methods as public API
4241
- - Document methods and parameters
4474
+ [Fragment updates](/up.link) automatically update the URL.
4475
+
4476
+
4477
+ Going back behavior .... configure.
4242
4478
 
4243
4479
  @class up.history
4244
4480
  */
@@ -4249,6 +4485,8 @@ We need to work on this page:
4249
4485
  u = up.util;
4250
4486
 
4251
4487
  /**
4488
+ Configures behavior when the user goes back or forward in browser history.
4489
+
4252
4490
  @property up.history.config
4253
4491
  @param {Array} [config.popTargets=['body']]
4254
4492
  An array of CSS selectors to replace when the user goes
@@ -4298,6 +4536,13 @@ We need to work on this page:
4298
4536
  isCurrentUrl = function(url) {
4299
4537
  return normalizeUrl(url) === currentUrl();
4300
4538
  };
4539
+
4540
+ /**
4541
+ Remembers the given URL so we can offer `up.history.previousUrl()`.
4542
+
4543
+ @function observeNewUrl
4544
+ @internal
4545
+ */
4301
4546
  observeNewUrl = function(url) {
4302
4547
  if (nextPreviousUrl) {
4303
4548
  previousUrl = nextPreviousUrl;
@@ -4401,9 +4646,9 @@ We need to work on this page:
4401
4646
  if (state != null ? state.fromUp : void 0) {
4402
4647
  url = currentUrl();
4403
4648
  return up.log.group("Restoring URL %s", url, function() {
4404
- var popSelector;
4649
+ var popSelector, replaced;
4405
4650
  popSelector = config.popTargets.join(', ');
4406
- return up.replace(popSelector, url, {
4651
+ replaced = up.replace(popSelector, url, {
4407
4652
  history: false,
4408
4653
  title: true,
4409
4654
  reveal: false,
@@ -4411,24 +4656,26 @@ We need to work on this page:
4411
4656
  saveScroll: false,
4412
4657
  restoreScroll: config.restoreScroll
4413
4658
  });
4659
+ return replaced.then(function() {
4660
+ url = currentUrl();
4661
+ return up.emit('up:history:restored', {
4662
+ url: url,
4663
+ message: "Restored location " + url
4664
+ });
4665
+ });
4414
4666
  });
4415
4667
  } else {
4416
4668
  return up.puts('Ignoring a state not pushed by Unpoly (%o)', state);
4417
4669
  }
4418
4670
  };
4419
4671
  pop = function(event) {
4420
- var state, url;
4672
+ var state;
4421
4673
  observeNewUrl(currentUrl());
4422
4674
  up.layout.saveScroll({
4423
4675
  url: previousUrl
4424
4676
  });
4425
4677
  state = event.originalEvent.state;
4426
- restoreStateOnPop(state);
4427
- url = currentUrl();
4428
- return up.emit('up:history:restored', {
4429
- url: url,
4430
- message: "Restored location " + url
4431
- });
4678
+ return restoreStateOnPop(state);
4432
4679
  };
4433
4680
 
4434
4681
  /**
@@ -4461,7 +4708,7 @@ We need to work on this page:
4461
4708
  Note that this will *not* call `location.back()`, but will set
4462
4709
  the link's `up-href` attribute to the actual, previous URL.
4463
4710
 
4464
- \#\#\# Under the hood
4711
+ \#\#\# Example
4465
4712
 
4466
4713
  This link ...
4467
4714
 
@@ -4528,7 +4775,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
4528
4775
  var slice = [].slice;
4529
4776
 
4530
4777
  up.layout = (function($) {
4531
- var SCROLL_PROMISE_KEY, anchoredRight, config, finishScrolling, fixedChildren, lastScrollTops, measureObstruction, reset, restoreScroll, reveal, revealOrRestoreScroll, saveScroll, scroll, scrollTops, u, viewportOf, viewportSelector, viewports, viewportsWithin;
4778
+ var SCROLL_PROMISE_KEY, anchoredRight, config, finishScrolling, firstHashTarget, fixedChildren, lastScrollTops, measureObstruction, reset, restoreScroll, reveal, revealHash, revealOrRestoreScroll, saveScroll, scroll, scrollTops, u, viewportOf, viewportSelector, viewports, viewportsWithin;
4532
4779
  u = up.util;
4533
4780
 
4534
4781
  /**
@@ -4808,6 +5055,31 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
4808
5055
  return u.resolvedDeferred();
4809
5056
  }
4810
5057
  };
5058
+
5059
+ /**
5060
+ [Reveals](/up.reveal) an element matching the `#hash` in the current URL.
5061
+
5062
+ Other than the default behavior found in browsers, `up.revealHash` works with
5063
+ [multiple viewports](/up-viewport) and honors [fixed elements](/up-fixed-top) obstructing the user's
5064
+ view of the viewport.
5065
+
5066
+ This is called automatically when the page loads initially.
5067
+
5068
+ @function up.layout.revealHash
5069
+ @experimental
5070
+ */
5071
+ revealHash = function() {
5072
+ var $match, hash;
5073
+ if (hash = up.browser.hash()) {
5074
+ if ($match = firstHashTarget(hash)) {
5075
+ return reveal($match);
5076
+ } else {
5077
+ return u.rejectedPromise();
5078
+ }
5079
+ } else {
5080
+ return u.resolvedPromise();
5081
+ }
5082
+ };
4811
5083
  viewportSelector = function() {
4812
5084
  return u.multiSelector(config.viewports);
4813
5085
  };
@@ -4986,7 +5258,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
4986
5258
  @internal
4987
5259
  */
4988
5260
  revealOrRestoreScroll = function(selectorOrElement, options) {
4989
- var $element, $target, id, parsed, revealOptions;
5261
+ var $element, $target, parsed, revealOptions;
4990
5262
  $element = $(selectorOrElement);
4991
5263
  if (options.restoreScroll) {
4992
5264
  return restoreScroll({
@@ -4996,13 +5268,9 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
4996
5268
  revealOptions = {};
4997
5269
  if (options.source) {
4998
5270
  parsed = u.parseUrl(options.source);
4999
- if (parsed.hash && parsed.hash !== '#') {
5000
- id = parsed.hash.substr(1);
5001
- $target = u.findWithSelf($element, "#" + id + ", a[name='" + id + "']");
5002
- if ($target.length) {
5003
- $element = $target;
5004
- revealOptions.top = true;
5005
- }
5271
+ if ($target = firstHashTarget(parsed.hash)) {
5272
+ $element = $target;
5273
+ revealOptions.top = true;
5006
5274
  }
5007
5275
  }
5008
5276
  return reveal($element, revealOptions);
@@ -5074,7 +5342,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
5074
5342
  Unpoly will then scroll the viewport far enough that the revealed element is fully visible.
5075
5343
 
5076
5344
  Instead of using this attribute,
5077
- you can also configure a selector in [`up.layout.config.fixedTop`](/up.layout.config#fixedTop).
5345
+ you can also configure a selector in [`up.layout.config.fixedTop`](/up.layout.config#config.fixedTop).
5078
5346
 
5079
5347
  \#\#\# Example
5080
5348
 
@@ -5094,7 +5362,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
5094
5362
  Unpoly will then scroll the viewport far enough that the revealed element is fully visible.
5095
5363
 
5096
5364
  Instead of using this attribute,
5097
- you can also configure a selector in [`up.layout.config.fixedBottom`](/up.layout.config#fixedBottom).
5365
+ you can also configure a selector in [`up.layout.config.fixedBottom`](/up.layout.config#config.fixedBottom).
5098
5366
 
5099
5367
  \#\#\# Example
5100
5368
 
@@ -5117,7 +5385,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
5117
5385
  with a CSS of `right: 0` with `position: fixed` or `position:absolute`.
5118
5386
 
5119
5387
  Instead of giving this attribute to any affected element,
5120
- you can also configure a selector in [`up.layout.config.anchoredRight`](/up.layout.config#anchoredRight).
5388
+ you can also configure a selector in [`up.layout.config.anchoredRight`](/up.layout.config#config.anchoredRight).
5121
5389
 
5122
5390
  \#\#\# Example
5123
5391
 
@@ -5138,10 +5406,23 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
5138
5406
  @selector [up-anchored=right]
5139
5407
  @stable
5140
5408
  */
5409
+
5410
+ /**
5411
+ @function up.layout.firstHashTarget
5412
+ @internal
5413
+ */
5414
+ firstHashTarget = function(hash) {
5415
+ if (hash = up.browser.hash(hash)) {
5416
+ return up.first("[id='" + hash + "'], a[name='" + hash + "']");
5417
+ }
5418
+ };
5419
+ up.on('up:app:booted', revealHash);
5141
5420
  up.on('up:framework:reset', reset);
5142
5421
  return {
5143
5422
  knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
5144
5423
  reveal: reveal,
5424
+ revealHash: revealHash,
5425
+ firstHashTarget: firstHashTarget,
5145
5426
  scroll: scroll,
5146
5427
  finishScrolling: finishScrolling,
5147
5428
  config: config,
@@ -5161,6 +5442,8 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
5161
5442
 
5162
5443
  up.reveal = up.layout.reveal;
5163
5444
 
5445
+ up.revealHash = up.layout.revealHash;
5446
+
5164
5447
  }).call(this);
5165
5448
 
5166
5449
  /**
@@ -5193,7 +5476,13 @@ is built from these functions. You can use them to extend Unpoly from your
5193
5476
  the linked JavaScript file.
5194
5477
  @param {String} [options.fallbacks=['body']]
5195
5478
  When a fragment updates cannot find the requested element, Unpoly will try this list of alternative selectors.
5479
+
5196
5480
  The first selector that matches an element in the current page (or response) will be used.
5481
+ If the response contains none of the selectors, an error message will be shown.
5482
+
5483
+ It is recommend to always keep `'body'` as the last selector in the last in the case
5484
+ your server or load balancer renders an error message that does not contain your
5485
+ application layout.
5197
5486
  @param {String} [options.fallbackTransition='none']
5198
5487
  The transition to use when using a fallback target.
5199
5488
  @stable
@@ -5456,14 +5745,14 @@ is built from these functions. You can use them to extend Unpoly from your
5456
5745
  */
5457
5746
  processResponse = function(isSuccess, selector, url, request, xhr, options) {
5458
5747
  var isReloadable, newRequest, query, titleFromXhr, urlFromServer;
5459
- options.method = u.normalizeMethod(u.option(u.methodFromXhr(xhr), options.method));
5748
+ options.method = u.normalizeMethod(u.option(up.protocol.methodFromXhr(xhr), options.method));
5460
5749
  isReloadable = options.method === 'GET';
5461
- if (urlFromServer = u.locationFromXhr(xhr)) {
5750
+ if (urlFromServer = up.protocol.locationFromXhr(xhr)) {
5462
5751
  url = urlFromServer;
5463
5752
  if (isSuccess && up.proxy.isCachable(request)) {
5464
5753
  newRequest = {
5465
5754
  url: url,
5466
- method: u.methodFromXhr(xhr),
5755
+ method: up.protocol.methodFromXhr(xhr),
5467
5756
  target: selector
5468
5757
  };
5469
5758
  up.proxy.alias(request, newRequest);
@@ -5504,7 +5793,7 @@ is built from these functions. You can use them to extend Unpoly from your
5504
5793
  options.history = false;
5505
5794
  }
5506
5795
  }
5507
- if (shouldExtractTitle(options) && (titleFromXhr = u.titleFromXhr(xhr))) {
5796
+ if (shouldExtractTitle(options) && (titleFromXhr = up.protocol.titleFromXhr(xhr))) {
5508
5797
  options.title = titleFromXhr;
5509
5798
  }
5510
5799
  if (options.preload) {
@@ -5952,7 +6241,7 @@ is built from these functions. You can use them to extend Unpoly from your
5952
6241
  up.first('.field:has(&)', $input); // returns the .field containing $input
5953
6242
  @return {jQuery|Undefined}
5954
6243
  The first element that is neither a ghost or being destroyed,
5955
- or `undefined` if no such element was given.
6244
+ or `undefined` if no such element was found.
5956
6245
  @experimental
5957
6246
  */
5958
6247
  first = function(selectorOrElement, options) {
@@ -7194,8 +7483,6 @@ Other Unpoly modules contain even more tricks to outsmart network latency:
7194
7483
  An array of uppercase HTTP method names. AJAX requests with one of these methods
7195
7484
  will be converted into a `POST` request and carry their original method as a `_method`
7196
7485
  parameter. This is to [prevent unexpected redirect behavior](https://makandracards.com/makandra/38347).
7197
- @param {String} [config.wrapMethodParam]
7198
- The name of the POST parameter when wrapping HTTP methods in a `POST` request.
7199
7486
  @param {Array<String>} [config.safeMethods]
7200
7487
  An array of uppercase HTTP method names that are considered idempotent.
7201
7488
  The proxy cache will only cache idempotent requests and will clear the entire
@@ -7209,7 +7496,6 @@ Other Unpoly modules contain even more tricks to outsmart network latency:
7209
7496
  cacheExpiry: 1000 * 60 * 5,
7210
7497
  maxRequests: 4,
7211
7498
  wrapMethods: ['PATCH', 'PUT', 'DELETE'],
7212
- wrapMethodParam: '_method',
7213
7499
  safeMethods: ['GET', 'OPTIONS', 'HEAD']
7214
7500
  });
7215
7501
  cacheKey = function(request) {
@@ -7335,7 +7621,7 @@ Other Unpoly modules contain even more tricks to outsmart network latency:
7335
7621
  @param {String} [request.timeout]
7336
7622
  A timeout in milliseconds for the request.
7337
7623
 
7338
- If [`up.proxy.config.maxRequests`](/up.proxy.config#maxRequests) is set, the timeout
7624
+ If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set, the timeout
7339
7625
  will not include the time spent waiting in the queue.
7340
7626
  @return
7341
7627
  A promise for the response that is API-compatible with the
@@ -7525,9 +7811,9 @@ Other Unpoly modules contain even more tricks to outsmart network latency:
7525
7811
  }));
7526
7812
  request = u.copy(request);
7527
7813
  request.headers || (request.headers = {});
7528
- request.headers['X-Up-Target'] = request.target;
7814
+ request.headers[up.protocol.config.targetHeader] = request.target;
7529
7815
  if (u.contains(config.wrapMethods, request.method)) {
7530
- request.data = u.appendRequestData(request.data, config.wrapMethodParam, request.method);
7816
+ request.data = u.appendRequestData(request.data, up.protocol.config.methodParam, request.method);
7531
7817
  request.method = 'POST';
7532
7818
  }
7533
7819
  if (u.isFormData(request.data)) {
@@ -8467,7 +8753,7 @@ open dialogs with sub-forms, etc. all without losing form state.
8467
8753
  options.headers || (options.headers = {});
8468
8754
  options.transition = false;
8469
8755
  options.failTransition = false;
8470
- options.headers['X-Up-Validate'] = options.validate;
8756
+ options.headers[up.protocol.config.validateHeader] = options.validate;
8471
8757
  if (!canAjaxSubmit) {
8472
8758
  return u.unresolvablePromise();
8473
8759
  }
@@ -8857,12 +9143,11 @@ open dialogs with sub-forms, etc. all without losing form state.
8857
9143
 
8858
9144
  \#\#\# Redirects
8859
9145
 
8860
- Unpoly requires two additional response headers to detect redirects,
9146
+ Unpoly requires an additional response headers to detect redirects,
8861
9147
  which are otherwise undetectable for an AJAX client.
8862
9148
 
8863
- When the form's action performs a redirect, the server should echo
8864
- the new request's URL as a response header `X-Up-Location`
8865
- and the request's HTTP method as `X-Up-Method: GET`.
9149
+ After the form's action performs a redirect, the next response should echo
9150
+ the new request's URL as a response header `X-Up-Location`.
8866
9151
 
8867
9152
  If you are using Unpoly via the `unpoly-rails` gem, these headers
8868
9153
  are set automatically for every request.
@@ -9024,7 +9309,7 @@ open dialogs with sub-forms, etc. all without losing form state.
9024
9309
  With the Bootstrap bindings, Unpoly will also look
9025
9310
  for a container with the `form-group` class.
9026
9311
 
9027
- You can change this default behavior by setting [`up.form.config.validateTargets`](/up.form.config#validateTargets):
9312
+ You can change this default behavior by setting [`up.form.config.validateTargets`](/up.form.config#config.validateTargets):
9028
9313
 
9029
9314
  // Always update the entire form containing the current field ("&")
9030
9315
  up.form.config.validateTargets = ['form &']