unpoly-rails 0.37.0 → 0.50.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 +4 -4
- data/CHANGELOG.md +127 -25
- data/LICENSE +1 -1
- data/README_RAILS.md +4 -2
- data/Rakefile +6 -1
- data/dist/unpoly.js +3192 -2198
- data/dist/unpoly.min.js +4 -3
- data/lib/assets/javascripts/unpoly/browser.coffee +51 -63
- data/lib/assets/javascripts/unpoly/bus.coffee +58 -33
- data/lib/assets/javascripts/unpoly/classes/cache.coffee +117 -0
- data/lib/assets/javascripts/unpoly/{dom → classes}/extract_cascade.coffee +3 -3
- data/lib/assets/javascripts/unpoly/{dom → classes}/extract_plan.coffee +1 -1
- data/lib/assets/javascripts/unpoly/classes/field_observer.coffee +57 -0
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +52 -0
- data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +95 -0
- data/lib/assets/javascripts/unpoly/classes/record.coffee +16 -0
- data/lib/assets/javascripts/unpoly/classes/request.coffee +228 -0
- data/lib/assets/javascripts/unpoly/classes/response.coffee +138 -0
- data/lib/assets/javascripts/unpoly/dom.coffee +151 -142
- data/lib/assets/javascripts/unpoly/feedback.coffee +67 -38
- data/lib/assets/javascripts/unpoly/form.coffee +156 -139
- data/lib/assets/javascripts/unpoly/history.coffee +22 -19
- data/lib/assets/javascripts/unpoly/layout.coffee +108 -90
- data/lib/assets/javascripts/unpoly/link.coffee +159 -158
- data/lib/assets/javascripts/unpoly/log.coffee +5 -5
- data/lib/assets/javascripts/unpoly/modal.coffee +93 -81
- data/lib/assets/javascripts/unpoly/motion.coffee +291 -250
- data/lib/assets/javascripts/unpoly/popup.coffee +67 -53
- data/lib/assets/javascripts/unpoly/protocol.coffee +67 -16
- data/lib/assets/javascripts/unpoly/proxy.coffee +282 -211
- data/lib/assets/javascripts/unpoly/rails.coffee +3 -14
- data/lib/assets/javascripts/unpoly/syntax.coffee +54 -49
- data/lib/assets/javascripts/unpoly/tooltip.coffee +18 -25
- data/lib/assets/javascripts/unpoly/util.coffee +236 -477
- data/lib/assets/javascripts/unpoly.coffee +1 -1
- data/lib/unpoly/rails/inspector.rb +67 -22
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile.lock +13 -13
- data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
- data/spec_app/app/assets/javascripts/jasmine_specs.coffee +1 -1
- data/spec_app/app/assets/stylesheets/jasmine_specs.sass +10 -0
- data/spec_app/app/controllers/binding_test_controller.rb +19 -2
- data/spec_app/app/controllers/method_test_controller.rb +16 -0
- data/spec_app/app/views/layouts/jasmine_rails/spec_runner.html.erb +20 -0
- data/spec_app/app/views/method_test/form_target.erb +17 -0
- data/spec_app/app/views/method_test/page1.erb +11 -0
- data/spec_app/app/views/method_test/page2.erb +6 -0
- data/spec_app/app/views/pages/start.erb +33 -19
- data/spec_app/config/initializers/assets.rb +5 -0
- data/spec_app/config/routes.rb +3 -0
- data/spec_app/spec/controllers/binding_test_controller_spec.rb +82 -27
- data/spec_app/spec/javascripts/helpers/agent_detector.coffee +17 -0
- data/spec_app/spec/javascripts/helpers/async_sequence.js.coffee +102 -0
- data/spec_app/spec/javascripts/helpers/last_request.js.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/mock_ajax.js.coffee +5 -2
- data/spec_app/spec/javascripts/helpers/promise_state.js +18 -0
- data/spec_app/spec/javascripts/helpers/protect_jasmine_runner.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/reset_history.js.coffee +22 -0
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +11 -3
- data/spec_app/spec/javascripts/helpers/show_lib_versions.coffee +10 -0
- data/spec_app/spec/javascripts/helpers/to_be_error.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_match_url.coffee +13 -0
- data/spec_app/spec/javascripts/helpers/trigger.js.coffee +13 -6
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +92 -33
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +64 -15
- data/spec_app/spec/javascripts/up/classes/.keep +0 -0
- data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +1 -0
- data/spec_app/spec/javascripts/up/dom_spec.js.coffee +759 -551
- data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +155 -82
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +490 -349
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +226 -179
- data/spec_app/spec/javascripts/up/layout_spec.js.coffee +253 -185
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +416 -270
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +459 -330
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +198 -153
- data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +9 -0
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +240 -175
- data/spec_app/spec/javascripts/up/protocol_spec.js.coffee +38 -0
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +777 -303
- data/spec_app/spec/javascripts/up/rails_spec.js.coffee +24 -8
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +40 -23
- data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +80 -66
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +227 -201
- data/spec_app/vendor/asset-libs/es6-promise-4.1.6/es6-promise.auto.js +1159 -0
- metadata +30 -7
- data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +0 -7
- data/spec_app/spec/javascripts/helpers/to_equal_url.coffee +0 -11
@@ -17,12 +17,12 @@ The cache holds up to 70 responses for 5 minutes. You can configure the cache si
|
|
17
17
|
Also the entire cache is cleared with every non-`GET` request (like `POST` or `PUT`).
|
18
18
|
|
19
19
|
If you need to make cache-aware requests from your [custom JavaScript](/up.syntax),
|
20
|
-
use [`up.
|
20
|
+
use [`up.request()`](/up.request).
|
21
21
|
|
22
22
|
\#\#\# Preloading links
|
23
23
|
|
24
24
|
Unpoly also lets you speed up reaction times by [preloading
|
25
|
-
links](/up-preload) when the user hovers over the click area (or puts the mouse/finger
|
25
|
+
links](/a-up-preload) when the user hovers over the click area (or puts the mouse/finger
|
26
26
|
down). This way the response will already be cached when
|
27
27
|
the user releases the mouse/finger.
|
28
28
|
|
@@ -35,8 +35,8 @@ that appears during a long-running request.
|
|
35
35
|
|
36
36
|
Other Unpoly modules contain even more tricks to outsmart network latency:
|
37
37
|
|
38
|
-
- [Instantaneous feedback for links that are currently loading](/up-active)
|
39
|
-
- [Follow links on `mousedown` instead of `click`](/up-instant)
|
38
|
+
- [Instantaneous feedback for links that are currently loading](/a.up-active)
|
39
|
+
- [Follow links on `mousedown` instead of `click`](/a-up-instant)
|
40
40
|
|
41
41
|
@class up.proxy
|
42
42
|
###
|
@@ -50,23 +50,23 @@ up.proxy = (($) ->
|
|
50
50
|
pendingCount = undefined
|
51
51
|
slowEventEmitted = undefined
|
52
52
|
|
53
|
-
|
53
|
+
queuedLoaders = []
|
54
54
|
|
55
55
|
###*
|
56
56
|
@property up.proxy.config
|
57
|
-
@param {
|
58
|
-
The number of milliseconds to wait before [`[up-preload]`](/up-preload)
|
57
|
+
@param {number} [config.preloadDelay=75]
|
58
|
+
The number of milliseconds to wait before [`[up-preload]`](/a-up-preload)
|
59
59
|
starts preloading.
|
60
|
-
@param {
|
60
|
+
@param {number} [config.cacheSize=70]
|
61
61
|
The maximum number of responses to cache.
|
62
62
|
If the size is exceeded, the oldest items will be dropped from the cache.
|
63
|
-
@param {
|
63
|
+
@param {number} [config.cacheExpiry=300000]
|
64
64
|
The number of milliseconds until a cache entry expires.
|
65
65
|
Defaults to 5 minutes.
|
66
|
-
@param {
|
66
|
+
@param {number} [config.slowDelay=300]
|
67
67
|
How long the proxy waits until emitting the [`up:proxy:slow` event](/up:proxy:slow).
|
68
68
|
Use this to prevent flickering of spinners.
|
69
|
-
@param {
|
69
|
+
@param {number} [config.maxRequests=4]
|
70
70
|
The maximum number of concurrent requests to allow before additional
|
71
71
|
requests are queued. This currently ignores preloading requests.
|
72
72
|
|
@@ -75,14 +75,14 @@ up.proxy = (($) ->
|
|
75
75
|
|
76
76
|
Note that your browser might [impose its own request limit](http://www.browserscope.org/?category=network)
|
77
77
|
regardless of what you configure here.
|
78
|
-
@param {Array<
|
78
|
+
@param {Array<string>} [config.wrapMethods]
|
79
79
|
An array of uppercase HTTP method names. AJAX requests with one of these methods
|
80
80
|
will be converted into a `POST` request and carry their original method as a `_method`
|
81
81
|
parameter. This is to [prevent unexpected redirect behavior](https://makandracards.com/makandra/38347).
|
82
|
-
@param {Array<
|
83
|
-
An array of uppercase HTTP method names that are considered
|
84
|
-
The proxy cache will only cache
|
85
|
-
cache after
|
82
|
+
@param {Array<string>} [config.safeMethods]
|
83
|
+
An array of uppercase HTTP method names that are considered [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1).
|
84
|
+
The proxy cache will only cache safe requests and will clear the entire
|
85
|
+
cache after an unsafe request.
|
86
86
|
@stable
|
87
87
|
###
|
88
88
|
config = u.config
|
@@ -94,19 +94,12 @@ up.proxy = (($) ->
|
|
94
94
|
wrapMethods: ['PATCH', 'PUT', 'DELETE']
|
95
95
|
safeMethods: ['GET', 'OPTIONS', 'HEAD']
|
96
96
|
|
97
|
-
|
98
|
-
normalizeRequest(request)
|
99
|
-
[ request.url,
|
100
|
-
request.method,
|
101
|
-
u.requestDataAsQuery(request.data),
|
102
|
-
request.target
|
103
|
-
].join('|')
|
104
|
-
|
105
|
-
cache = u.cache
|
97
|
+
cache = new up.Cache
|
106
98
|
size: -> config.cacheSize
|
107
99
|
expiry: -> config.cacheExpiry
|
108
|
-
key: cacheKey
|
109
|
-
|
100
|
+
key: (request) -> up.Request.wrap(request).cacheKey()
|
101
|
+
cachable: (request) -> up.Request.wrap(request).isCachable()
|
102
|
+
# logPrefix: 'up.proxy'
|
110
103
|
|
111
104
|
###*
|
112
105
|
Returns a cached response for the given request.
|
@@ -114,24 +107,30 @@ up.proxy = (($) ->
|
|
114
107
|
Returns `undefined` if the given request is not currently cached.
|
115
108
|
|
116
109
|
@function up.proxy.get
|
117
|
-
@return {Promise}
|
118
|
-
A promise for the response
|
119
|
-
promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
|
110
|
+
@return {Promise<up.Response>}
|
111
|
+
A promise for the response.
|
120
112
|
@experimental
|
121
113
|
###
|
122
114
|
get = (request) ->
|
123
|
-
request =
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
115
|
+
request = up.Request.wrap(request)
|
116
|
+
candidates = [request]
|
117
|
+
|
118
|
+
if request.target != 'html'
|
119
|
+
# Since <html> is the root tag, a request for the `html` selector
|
120
|
+
# will contain all other selectors.
|
121
|
+
requestForHtml = request.copy(target: 'html')
|
122
|
+
candidates.push(requestForHtml)
|
123
|
+
|
124
|
+
# Although <body> is not the root tag, we consider it the selector developers
|
125
|
+
# will use when they want to replace the entire page. Hence we consider it
|
126
|
+
# a suitable match for all other selectors, including `html`.
|
127
|
+
if request.target != 'body'
|
128
|
+
requestForBody = request.copy(target: 'body')
|
129
|
+
candidates.push(requestForBody)
|
130
|
+
|
131
|
+
for candidate in candidates
|
132
|
+
if response = cache.get(candidate)
|
133
|
+
return response
|
135
134
|
|
136
135
|
cancelPreloadDelay = ->
|
137
136
|
clearTimeout(preloadDelayTimer)
|
@@ -149,108 +148,100 @@ up.proxy = (($) ->
|
|
149
148
|
config.reset()
|
150
149
|
cache.clear()
|
151
150
|
slowEventEmitted = false
|
152
|
-
|
151
|
+
queuedLoaders = []
|
153
152
|
|
154
153
|
reset()
|
155
154
|
|
156
|
-
normalizeRequest = (request) ->
|
157
|
-
unless request._normalized
|
158
|
-
request.method = u.normalizeMethod(request.method)
|
159
|
-
request.url = u.normalizeUrl(request.url) if request.url
|
160
|
-
request.target ||= 'body'
|
161
|
-
request._normalized = true
|
162
|
-
request
|
163
|
-
|
164
155
|
###*
|
165
|
-
Makes
|
166
|
-
If the response was already cached, returns the HTML instantly.
|
167
|
-
|
168
|
-
If requesting a URL that is not read-only, the response will
|
169
|
-
not be cached and the entire cache will be cleared.
|
170
|
-
Only requests with a method of `GET`, `OPTIONS` and `HEAD`
|
171
|
-
are considered to be read-only.
|
156
|
+
Makes an AJAX request to the given URL.
|
172
157
|
|
173
158
|
\#\#\# Example
|
174
159
|
|
175
|
-
up.
|
176
|
-
console.log('The response
|
177
|
-
}).fail(function(
|
160
|
+
up.request('/search', data: { query: 'sunshine' }).then(function(response) {
|
161
|
+
console.log('The response text is %o', response.text);
|
162
|
+
}).fail(function() {
|
178
163
|
console.error('The request failed');
|
179
164
|
});
|
180
165
|
|
166
|
+
\#\#\# Caching
|
167
|
+
|
168
|
+
All responses are cached by default. If requesting a URL with a non-`GET` method, the response will
|
169
|
+
not be cached and the entire cache will be cleared.
|
170
|
+
|
171
|
+
You can configure caching with the [`up.proxy.config`](/up.proxy.config) property.
|
172
|
+
|
181
173
|
\#\#\# Events
|
182
174
|
|
183
175
|
If a network connection is attempted, the proxy will emit
|
184
176
|
a [`up:proxy:load`](/up:proxy:load) event with the `request` as its argument.
|
185
|
-
Once the response is received, a [`up:proxy:
|
177
|
+
Once the response is received, a [`up:proxy:loaded`](/up:proxy:loaded) event will
|
186
178
|
be emitted.
|
187
179
|
|
188
|
-
@function up.
|
189
|
-
@param {
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
If set to `false` a network connection will always be attempted.
|
195
|
-
@param {Object} [request.headers={}]
|
196
|
-
An object of additional header key/value pairs to send along
|
197
|
-
with the request.
|
198
|
-
@param {Object} [request.data={}]
|
199
|
-
An object of request parameters.
|
200
|
-
@param {String} [request.url]
|
180
|
+
@function up.request
|
181
|
+
@param {string} [url]
|
182
|
+
The URL for the request.
|
183
|
+
|
184
|
+
Instead of passing the URL as a string argument, you can also pass it as an `{ url }` option.
|
185
|
+
@param {string} [options.url]
|
201
186
|
You can omit the first string argument and pass the URL as
|
202
187
|
a `request` property instead.
|
203
|
-
@param {
|
204
|
-
|
188
|
+
@param {string} [options.method='GET']
|
189
|
+
The HTTP method for the options.
|
190
|
+
@param {boolean} [options.cache]
|
191
|
+
Whether to use a cached response for [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
|
192
|
+
requests, if available. If set to `false` a network connection will always be attempted.
|
193
|
+
@param {Object} [options.headers={}]
|
194
|
+
An object of additional HTTP headers.
|
195
|
+
@param {Object|Array|FormData} [options.data={}]
|
196
|
+
Parameters that should be sent as the request's payload.
|
197
|
+
|
198
|
+
Parameters may be passed as one of the following forms:
|
199
|
+
|
200
|
+
1. An object where keys are param names and the values are param values
|
201
|
+
2. An array of `{ name: 'param-name', value: 'param-value' }` objects
|
202
|
+
3. A [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object
|
203
|
+
@param {string} [options.timeout]
|
204
|
+
A timeout in milliseconds.
|
205
205
|
|
206
206
|
If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set, the timeout
|
207
207
|
will not include the time spent waiting in the queue.
|
208
|
-
@
|
209
|
-
|
210
|
-
|
208
|
+
@param {string} [options.target='body']
|
209
|
+
The CSS selector that will be sent as an [`X-Up-Target` header](/up.protocol#optimizing-responses).
|
210
|
+
@param {string} [options.failTarget='body']
|
211
|
+
The CSS selector that will be sent as an [`X-Up-Fail-Target` header](/up.protocol#optimizing-responses).
|
212
|
+
@return {Promise<up.Response>}
|
213
|
+
A promise for the response.
|
211
214
|
@stable
|
212
215
|
###
|
213
|
-
|
214
|
-
|
216
|
+
makeRequest = (args...) ->
|
215
217
|
options = u.extractOptions(args)
|
216
218
|
options.url = args[0] if u.isGiven(args[0])
|
217
219
|
|
218
|
-
forceCache = (options.cache == true)
|
219
220
|
ignoreCache = (options.cache == false)
|
220
221
|
|
221
|
-
request =
|
222
|
-
'url',
|
223
|
-
'method',
|
224
|
-
'data',
|
225
|
-
'target',
|
226
|
-
'headers',
|
227
|
-
'timeout',
|
228
|
-
'_normalized'
|
229
|
-
|
230
|
-
request = normalizeRequest(request)
|
231
|
-
|
232
|
-
pending = true
|
222
|
+
request = up.Request.wrap(options)
|
233
223
|
|
234
224
|
# Non-GET requests always touch the network
|
235
225
|
# unless `options.cache` is explicitly set to `true`.
|
236
226
|
# These requests are never cached.
|
237
|
-
if !
|
227
|
+
if !request.isSafe()
|
228
|
+
# We clear the entire cache before an unsafe request, since we
|
229
|
+
# assume the user is writing a change.
|
238
230
|
clear()
|
239
|
-
|
231
|
+
|
240
232
|
# If we have an existing promise matching this new request,
|
241
233
|
# we use it unless `options.cache` is explicitly set to `false`.
|
242
|
-
|
243
|
-
else if (promise = get(request)) && !ignoreCache
|
234
|
+
if !ignoreCache && (promise = get(request))
|
244
235
|
up.puts 'Re-using cached response for %s %s', request.method, request.url
|
245
|
-
pending = (promise.state() == 'pending')
|
246
|
-
# If no existing promise is available, we make a network request.
|
247
236
|
else
|
237
|
+
# If no existing promise is available, we make a network request.
|
248
238
|
promise = loadOrQueue(request)
|
249
239
|
set(request, promise)
|
250
|
-
#
|
251
|
-
promise.
|
240
|
+
# Uncache failed requests
|
241
|
+
promise.catch (e) ->
|
242
|
+
remove(request)
|
252
243
|
|
253
|
-
if
|
244
|
+
if !options.preload
|
254
245
|
# This might actually make `pendingCount` higher than the actual
|
255
246
|
# number of outstanding requests. However, we need to cover the
|
256
247
|
# following case:
|
@@ -262,27 +253,69 @@ up.proxy = (($) ->
|
|
262
253
|
# - The request finishes.
|
263
254
|
# This triggers `up:proxy:recover`.
|
264
255
|
loadStarted()
|
265
|
-
|
256
|
+
u.always promise, loadEnded
|
266
257
|
|
267
258
|
promise
|
268
259
|
|
269
260
|
###*
|
270
|
-
|
271
|
-
Even if this returns `true`, only idempodent requests will be
|
272
|
-
cached by default.
|
261
|
+
Makes an AJAX request to the given URL and caches the response.
|
273
262
|
|
274
|
-
|
275
|
-
|
263
|
+
The function returns a promise that fulfills with the response text.
|
264
|
+
|
265
|
+
\#\#\# Example
|
266
|
+
|
267
|
+
up.request('/search', data: { query: 'sunshine' }).then(function(text) {
|
268
|
+
console.log('The response text is %o', text);
|
269
|
+
}).fail(function() {
|
270
|
+
console.error('The request failed');
|
271
|
+
});
|
272
|
+
|
273
|
+
@function up.ajax
|
274
|
+
@param {string} [url]
|
275
|
+
The URL for the request.
|
276
|
+
|
277
|
+
Instead of passing the URL as a string argument, you can also pass it as an `{ url }` option.
|
278
|
+
@param {string} [request.url]
|
279
|
+
You can omit the first string argument and pass the URL as
|
280
|
+
a `request` property instead.
|
281
|
+
@param {string} [request.method='GET']
|
282
|
+
The HTTP method for the request.
|
283
|
+
@param {boolean} [request.cache]
|
284
|
+
Whether to use a cached response for [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
|
285
|
+
requests, if available. If set to `false` a network connection will always be attempted.
|
286
|
+
@param {Object} [request.headers={}]
|
287
|
+
An object of additional header key/value pairs to send along
|
288
|
+
with the request.
|
289
|
+
@param {Object|Array|FormData} [options.data]
|
290
|
+
Parameters that should be sent as the request's payload.
|
291
|
+
|
292
|
+
Parameters may be passed as one of the following forms:
|
293
|
+
|
294
|
+
1. An object where keys are param names and the values are param values
|
295
|
+
2. An array of `{ name: 'param-name', value: 'param-value' }` objects
|
296
|
+
3. A [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object
|
297
|
+
@param {string} [request.timeout]
|
298
|
+
A timeout in milliseconds for the request.
|
299
|
+
|
300
|
+
If [`up.proxy.config.maxRequests`](/up.proxy.config#config.maxRequests) is set, the timeout
|
301
|
+
will not include the time spent waiting in the queue.
|
302
|
+
@return {Promise<string>}
|
303
|
+
A promise for the response text.
|
304
|
+
@deprecated
|
305
|
+
Use [`up.request()`](/up.request) instead.
|
276
306
|
###
|
277
|
-
|
278
|
-
|
307
|
+
ajax = (args...) ->
|
308
|
+
up.log.warn('up.ajax() has been deprecated. Use up.request() instead.')
|
309
|
+
new Promise (resolve, reject) ->
|
310
|
+
pickResponseText = (response) -> resolve(response.text)
|
311
|
+
makeRequest(args...).then(pickResponseText, reject)
|
279
312
|
|
280
313
|
###*
|
281
314
|
Returns `true` if the proxy is not currently waiting
|
282
315
|
for a request to finish. Returns `false` otherwise.
|
283
316
|
|
284
317
|
@function up.proxy.isIdle
|
285
|
-
@return {
|
318
|
+
@return {boolean}
|
286
319
|
Whether the proxy is idle
|
287
320
|
@experimental
|
288
321
|
###
|
@@ -294,7 +327,7 @@ up.proxy = (($) ->
|
|
294
327
|
for a request to finish. Returns `false` otherwise.
|
295
328
|
|
296
329
|
@function up.proxy.isBusy
|
297
|
-
@return {
|
330
|
+
@return {boolean}
|
298
331
|
Whether the proxy is busy
|
299
332
|
@experimental
|
300
333
|
###
|
@@ -302,19 +335,19 @@ up.proxy = (($) ->
|
|
302
335
|
pendingCount > 0
|
303
336
|
|
304
337
|
loadStarted = ->
|
305
|
-
wasIdle = isIdle()
|
306
338
|
pendingCount += 1
|
307
|
-
|
339
|
+
unless slowDelayTimer
|
308
340
|
# Since the emission of up:proxy:slow might be delayed by config.slowDelay,
|
309
341
|
# we wrap the mission in a function for scheduling below.
|
310
342
|
emission = ->
|
311
343
|
if isBusy() # a fast response might have beaten the delay
|
312
|
-
up.emit('up:proxy:slow', message: 'Proxy is
|
344
|
+
up.emit('up:proxy:slow', message: 'Proxy is slow to respond')
|
313
345
|
slowEventEmitted = true
|
314
346
|
slowDelayTimer = u.setTimer(config.slowDelay, emission)
|
315
347
|
|
348
|
+
|
316
349
|
###*
|
317
|
-
This event is [emitted](/up.emit) when [AJAX requests](/up.
|
350
|
+
This event is [emitted](/up.emit) when [AJAX requests](/up.request)
|
318
351
|
are taking long to finish.
|
319
352
|
|
320
353
|
By default Unpoly will wait 300 ms for an AJAX request to finish
|
@@ -367,12 +400,15 @@ up.proxy = (($) ->
|
|
367
400
|
|
368
401
|
loadEnded = ->
|
369
402
|
pendingCount -= 1
|
370
|
-
|
371
|
-
|
372
|
-
|
403
|
+
|
404
|
+
if isIdle()
|
405
|
+
cancelSlowDelay()
|
406
|
+
if slowEventEmitted
|
407
|
+
up.emit('up:proxy:recover', message: 'Proxy has recovered from slow response')
|
408
|
+
slowEventEmitted = false
|
373
409
|
|
374
410
|
###*
|
375
|
-
This event is [emitted](/up.emit) when [AJAX requests](/up.
|
411
|
+
This event is [emitted](/up.emit) when [AJAX requests](/up.request)
|
376
412
|
have [taken long to finish](/up:proxy:slow), but have finished now.
|
377
413
|
|
378
414
|
See [`up:proxy:slow`](/up:proxy:slow) for more documentation on
|
@@ -391,49 +427,88 @@ up.proxy = (($) ->
|
|
391
427
|
|
392
428
|
queue = (request) ->
|
393
429
|
up.puts('Queuing request for %s %s', request.method, request.url)
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
queuedRequests.push(entry)
|
399
|
-
deferred.promise()
|
430
|
+
loader = -> load(request)
|
431
|
+
loader = u.previewable(loader)
|
432
|
+
queuedLoaders.push(loader)
|
433
|
+
loader.promise
|
400
434
|
|
401
435
|
load = (request) ->
|
402
|
-
|
436
|
+
eventProps =
|
437
|
+
request: request
|
438
|
+
message: ['Loading %s %s', request.method, request.url]
|
403
439
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
440
|
+
if up.bus.nobodyPrevents('up:proxy:load', eventProps)
|
441
|
+
responsePromise = request.send()
|
442
|
+
u.always responsePromise, responseReceived
|
443
|
+
u.always responsePromise, pokeQueue
|
444
|
+
responsePromise
|
445
|
+
else
|
446
|
+
u.microtask(pokeQueue)
|
447
|
+
Promise.reject(new Error('Event up:proxy:load was prevented'))
|
408
448
|
|
409
|
-
|
410
|
-
|
449
|
+
###*
|
450
|
+
This event is [emitted](/up.emit) before an [AJAX request](/up.request)
|
451
|
+
is sent over the network.
|
411
452
|
|
412
|
-
|
413
|
-
|
414
|
-
|
453
|
+
@event up:proxy:load
|
454
|
+
@param {up.Request} event.request
|
455
|
+
@param event.preventDefault()
|
456
|
+
Event listeners may call this method to prevent the request from being sent.
|
457
|
+
@experimental
|
458
|
+
###
|
415
459
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
460
|
+
registerAliasForRedirect = (response) ->
|
461
|
+
request = response.request
|
462
|
+
if request.url != response.url
|
463
|
+
newRequest = request.copy(
|
464
|
+
method: response.method
|
465
|
+
url: response.url
|
466
|
+
)
|
467
|
+
up.proxy.alias(request, newRequest)
|
468
|
+
|
469
|
+
responseReceived = (response) ->
|
470
|
+
if response.isFatalError()
|
471
|
+
up.emit 'up:proxy:fatal',
|
472
|
+
message: 'Fatal error during request'
|
473
|
+
request: response.request
|
474
|
+
response: response
|
475
|
+
else
|
476
|
+
registerAliasForRedirect(response) unless response.isError()
|
477
|
+
up.emit 'up:proxy:loaded',
|
478
|
+
message: ['Server responded with HTTP %d (%d bytes)', response.status, response.text.length]
|
479
|
+
request: response.request
|
480
|
+
response: response
|
421
481
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
482
|
+
###*
|
483
|
+
This event is [emitted](/up.emit) when the response to an
|
484
|
+
[AJAX request](/up.request) has been received.
|
485
|
+
|
486
|
+
Note that this event will also be emitted when the server signals an
|
487
|
+
error with an HTTP status like `500`. Only if the request
|
488
|
+
encounters a fatal error (like a loss of network connectivity),
|
489
|
+
[`up:proxy:fatal`](/up:proxy:fatal) is emitted instead.
|
490
|
+
|
491
|
+
@event up:proxy:loaded
|
492
|
+
@param {up.Request} event.request
|
493
|
+
@param {up.Response} event.response
|
494
|
+
@experimental
|
495
|
+
###
|
426
496
|
|
427
|
-
|
428
|
-
|
429
|
-
|
497
|
+
###*
|
498
|
+
This event is [emitted](/up.emit) when an [AJAX request](/up.request)
|
499
|
+
encounters fatal error like a timeout or loss of network connectivity.
|
500
|
+
|
501
|
+
Note that this event will *not* be emitted when the server produces an
|
502
|
+
error message with an HTTP status like `500`. When the server can produce
|
503
|
+
any response, [`up:proxy:loaded`](/up:proxy:loaded) is emitted instead.
|
504
|
+
|
505
|
+
@event up:proxy:fatal
|
506
|
+
###
|
430
507
|
|
431
508
|
pokeQueue = ->
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
promise.fail (args...) -> entry.deferred.reject(args...)
|
436
|
-
return
|
509
|
+
queuedLoaders.shift()?()
|
510
|
+
# Don't return the promise from the loader above
|
511
|
+
return undefined
|
437
512
|
|
438
513
|
###*
|
439
514
|
Makes the proxy assume that `newRequest` has the same response as the
|
@@ -453,12 +528,11 @@ up.proxy = (($) ->
|
|
453
528
|
Manually stores a promise for the response to the given request.
|
454
529
|
|
455
530
|
@function up.proxy.set
|
456
|
-
@param {
|
457
|
-
@param {
|
458
|
-
@param {
|
459
|
-
@param {Promise} response
|
460
|
-
A promise for the response
|
461
|
-
promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
|
531
|
+
@param {string} request.url
|
532
|
+
@param {string} [request.method='GET']
|
533
|
+
@param {string} [request.target='body']
|
534
|
+
@param {Promise<up.Response>} response
|
535
|
+
A promise for the response.
|
462
536
|
@experimental
|
463
537
|
###
|
464
538
|
set = cache.set
|
@@ -470,9 +544,9 @@ up.proxy = (($) ->
|
|
470
544
|
automatically removes cache entries.
|
471
545
|
|
472
546
|
@function up.proxy.remove
|
473
|
-
@param {
|
474
|
-
@param {
|
475
|
-
@param {
|
547
|
+
@param {string} request.url
|
548
|
+
@param {string} [request.method='GET']
|
549
|
+
@param {string} [request.target='body']
|
476
550
|
@experimental
|
477
551
|
###
|
478
552
|
remove = cache.remove
|
@@ -481,41 +555,18 @@ up.proxy = (($) ->
|
|
481
555
|
Removes all cache entries.
|
482
556
|
|
483
557
|
Unpoly also automatically clears the cache whenever it processes
|
484
|
-
a request with
|
558
|
+
a request with an [unsafe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
|
559
|
+
HTTP method like `POST`.
|
485
560
|
|
486
561
|
@function up.proxy.clear
|
487
562
|
@stable
|
488
563
|
###
|
489
564
|
clear = cache.clear
|
490
565
|
|
491
|
-
|
492
|
-
This event is [emitted](/up.emit) before an [AJAX request](/up.ajax)
|
493
|
-
is starting to load.
|
494
|
-
|
495
|
-
@event up:proxy:load
|
496
|
-
@param event.url
|
497
|
-
@param event.method
|
498
|
-
@param event.target
|
499
|
-
@experimental
|
500
|
-
###
|
501
|
-
|
502
|
-
###*
|
503
|
-
This event is [emitted](/up.emit) when the response to an [AJAX request](/up.ajax)
|
504
|
-
has been received.
|
505
|
-
|
506
|
-
@event up:proxy:received
|
507
|
-
@param event.url
|
508
|
-
@param event.method
|
509
|
-
@param event.target
|
510
|
-
@experimental
|
511
|
-
###
|
512
|
-
|
513
|
-
isIdempotent = (request) ->
|
514
|
-
normalizeRequest(request)
|
515
|
-
u.contains(config.safeMethods, request.method)
|
566
|
+
up.bus.renamedEvent('up:proxy:received', 'up:proxy:loaded')
|
516
567
|
|
517
568
|
checkPreload = ($link) ->
|
518
|
-
delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay
|
569
|
+
delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay
|
519
570
|
unless $link.is($waitingLink)
|
520
571
|
$waitingLink = $link
|
521
572
|
cancelPreloadDelay()
|
@@ -528,60 +579,80 @@ up.proxy = (($) ->
|
|
528
579
|
preloadDelayTimer = setTimeout(block, delay)
|
529
580
|
|
530
581
|
###*
|
582
|
+
Preloads the given link.
|
583
|
+
|
584
|
+
When the link is clicked later, the response will already be cached,
|
585
|
+
making the interaction feel instant.
|
586
|
+
|
531
587
|
@function up.proxy.preload
|
532
|
-
@param {
|
588
|
+
@param {string|Element|jQuery}
|
533
589
|
The element whose destination should be preloaded.
|
534
590
|
@return
|
535
|
-
A promise that will be
|
591
|
+
A promise that will be fulfilled when the request was loaded and cached
|
536
592
|
@experimental
|
537
593
|
###
|
538
|
-
preload = (linkOrSelector
|
594
|
+
preload = (linkOrSelector) ->
|
539
595
|
$link = $(linkOrSelector)
|
540
|
-
options = u.options(options)
|
541
596
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
up.follow($link, options)
|
597
|
+
if up.link.isSafe($link)
|
598
|
+
up.log.group "Preloading link %o", $link.get(0), ->
|
599
|
+
variant = up.link.followVariantForLink($link)
|
600
|
+
variant.preloadLink($link)
|
547
601
|
else
|
548
|
-
|
549
|
-
|
602
|
+
Promise.reject(new Error("Won't preload unsafe link"))
|
603
|
+
|
604
|
+
###*
|
605
|
+
@internal
|
606
|
+
###
|
607
|
+
isSafeMethod = (method) ->
|
608
|
+
u.contains(config.safeMethods, method)
|
609
|
+
|
610
|
+
###*
|
611
|
+
@internal
|
612
|
+
###
|
613
|
+
wrapMethod = (method, data, appendOpts) ->
|
614
|
+
if u.contains(config.wrapMethods, method)
|
615
|
+
data = u.appendRequestData(data, up.protocol.config.methodParam, method, appendOpts)
|
616
|
+
method = 'POST'
|
617
|
+
[method, data]
|
550
618
|
|
551
619
|
###*
|
552
620
|
Links with an `up-preload` attribute will silently fetch their target
|
553
621
|
when the user hovers over the click area, or when the user puts her
|
554
|
-
mouse/finger down (before releasing).
|
555
|
-
|
622
|
+
mouse/finger down (before releasing).
|
623
|
+
|
624
|
+
When the link is clicked later, the response will already be cached,
|
556
625
|
making the interaction feel instant.
|
557
626
|
|
558
|
-
@selector [up-preload]
|
627
|
+
@selector a[up-preload]
|
559
628
|
@param [up-delay=75]
|
560
629
|
The number of milliseconds to wait between hovering
|
561
630
|
and preloading. Increasing this will lower the load in your server,
|
562
631
|
but will also make the interaction feel less instant.
|
563
632
|
@stable
|
564
633
|
###
|
565
|
-
up.on 'mouseover mousedown touchstart', '[up-preload]', (event, $element) ->
|
566
|
-
# Don't do anything if we are hovering over the child
|
567
|
-
#
|
568
|
-
|
569
|
-
unless up.link.childClicked(event, $element)
|
634
|
+
up.on 'mouseover mousedown touchstart', 'a[up-preload], [up-href][up-preload]', (event, $element) ->
|
635
|
+
# Don't do anything if we are hovering over the child of a link.
|
636
|
+
# The actual link will receive the event and bubble in a second.
|
637
|
+
if !up.link.childClicked(event, $element) && up.link.isSafe($element)
|
570
638
|
checkPreload($element)
|
571
639
|
|
572
640
|
up.on 'up:framework:reset', reset
|
573
641
|
|
574
642
|
preload: preload
|
575
643
|
ajax: ajax
|
644
|
+
request: makeRequest
|
576
645
|
get: get
|
577
646
|
alias: alias
|
578
647
|
clear: clear
|
579
648
|
remove: remove
|
580
649
|
isIdle: isIdle
|
581
650
|
isBusy: isBusy
|
582
|
-
|
651
|
+
isSafeMethod: isSafeMethod
|
652
|
+
wrapMethod: wrapMethod
|
583
653
|
config: config
|
584
654
|
|
585
655
|
)(jQuery)
|
586
656
|
|
587
657
|
up.ajax = up.proxy.ajax
|
658
|
+
up.request = up.proxy.request
|