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
@@ -2,14 +2,16 @@
|
|
2
2
|
Browser support
|
3
3
|
===============
|
4
4
|
|
5
|
-
Unpoly supports all modern browsers.
|
5
|
+
Unpoly supports all modern browsers.
|
6
6
|
|
7
|
-
|
7
|
+
Chrome, Firefox, Edge, Safari
|
8
8
|
: Full support
|
9
9
|
|
10
|
-
|
11
|
-
:
|
10
|
+
Internet Explorer 11
|
11
|
+
: Full support with a `Promise` polyfill like [es6-promise](https://github.com/stefanpenner/es6-promise) (2.4 KB).
|
12
12
|
|
13
|
+
Internet Explorer 10 or lower
|
14
|
+
: Unpoly prevents itself from booting itself, leaving you with a classic server-side application.
|
13
15
|
|
14
16
|
@class up.browser
|
15
17
|
###
|
@@ -18,30 +20,15 @@ up.browser = (($) ->
|
|
18
20
|
u = up.util
|
19
21
|
|
20
22
|
###*
|
21
|
-
@method up.browser.
|
22
|
-
@param {
|
23
|
-
@param {
|
23
|
+
@method up.browser.navigate
|
24
|
+
@param {string} url
|
25
|
+
@param {string} [options.method='get']
|
24
26
|
@param {Object|Array} [options.data]
|
25
27
|
@internal
|
26
28
|
###
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
query = u.requestDataAsQuery(options.data)
|
31
|
-
url = "#{url}?#{query}" if query
|
32
|
-
setLocationHref(url)
|
33
|
-
else
|
34
|
-
$form = $("<form method='post' action='#{url}' class='up-page-loader'></form>")
|
35
|
-
addField = (field) ->
|
36
|
-
$field = $('<input type="hidden">')
|
37
|
-
$field.attr(field)
|
38
|
-
$field.appendTo($form)
|
39
|
-
addField(name: up.protocol.config.methodParam, value: method)
|
40
|
-
if csrfField = up.rails.csrfField()
|
41
|
-
addField(csrfField)
|
42
|
-
u.each u.requestDataAsArray(options.data), addField
|
43
|
-
$form.hide().appendTo('body')
|
44
|
-
submitForm($form)
|
29
|
+
navigate = (url, options = {}) ->
|
30
|
+
request = new up.Request(u.merge(options, { url }))
|
31
|
+
request.navigate()
|
45
32
|
|
46
33
|
###*
|
47
34
|
For mocking in specs.
|
@@ -51,14 +38,6 @@ up.browser = (($) ->
|
|
51
38
|
submitForm = ($form) ->
|
52
39
|
$form.submit()
|
53
40
|
|
54
|
-
###*
|
55
|
-
For mocking in specs.
|
56
|
-
|
57
|
-
@method setLocationHref
|
58
|
-
###
|
59
|
-
setLocationHref = (url) ->
|
60
|
-
location.href = url
|
61
|
-
|
62
41
|
###*
|
63
42
|
A cross-browser way to interact with `console.log`, `console.error`, etc.
|
64
43
|
|
@@ -155,10 +134,10 @@ up.browser = (($) ->
|
|
155
134
|
a request method other than GET.
|
156
135
|
|
157
136
|
@function up.browser.canPushState
|
158
|
-
@return {
|
137
|
+
@return {boolean}
|
159
138
|
@experimental
|
160
139
|
###
|
161
|
-
canPushState =
|
140
|
+
canPushState = ->
|
162
141
|
# We cannot use pushState if the initial request method is a POST for two reasons:
|
163
142
|
#
|
164
143
|
# 1. Unpoly replaces the initial state so it can handle the pop event when the
|
@@ -168,7 +147,8 @@ up.browser = (($) ->
|
|
168
147
|
# 2. Some browsers have a bug where the initial request method is used for all
|
169
148
|
# subsequently pushed states. That means if the user reloads the page on a later
|
170
149
|
# GET state, the browser will wrongly attempt a POST request.
|
171
|
-
#
|
150
|
+
# This issue affects Safari 9 and 10 (last tested in 2017-08).
|
151
|
+
# Modern Firefoxes, Chromes and IE10+ don't have this behavior.
|
172
152
|
#
|
173
153
|
# The way that we work around this is that we don't support pushState if the
|
174
154
|
# initial request method was anything other than GET (but allow the rest of the
|
@@ -185,52 +165,62 @@ up.browser = (($) ->
|
|
185
165
|
animation by instantly jumping to the last frame.
|
186
166
|
|
187
167
|
@function up.browser.canCssTransition
|
188
|
-
@return {
|
168
|
+
@return {boolean}
|
189
169
|
@internal
|
190
170
|
###
|
191
|
-
canCssTransition =
|
171
|
+
canCssTransition = ->
|
192
172
|
'transition' of document.documentElement.style
|
193
173
|
|
194
174
|
###*
|
195
175
|
Returns whether this browser supports the DOM event [`input`](https://developer.mozilla.org/de/docs/Web/Events/input).
|
196
176
|
|
197
177
|
@function up.browser.canInputEvent
|
198
|
-
@return {
|
178
|
+
@return {boolean}
|
199
179
|
@internal
|
200
180
|
###
|
201
|
-
canInputEvent =
|
181
|
+
canInputEvent = ->
|
202
182
|
'oninput' of document.createElement('input')
|
203
183
|
|
184
|
+
###*
|
185
|
+
Returns whether this browser supports promises.
|
186
|
+
|
187
|
+
@function up.browser.canPromise
|
188
|
+
@return {boolean}
|
189
|
+
@internal
|
190
|
+
###
|
191
|
+
canPromise = ->
|
192
|
+
!!window.Promise
|
193
|
+
|
204
194
|
###*
|
205
195
|
Returns whether this browser supports the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
|
206
196
|
interface.
|
207
197
|
|
208
198
|
@function up.browser.canFormData
|
209
|
-
@return {
|
199
|
+
@return {boolean}
|
210
200
|
@experimental
|
211
201
|
###
|
212
|
-
canFormData =
|
202
|
+
canFormData = ->
|
213
203
|
!!window.FormData
|
214
204
|
|
215
205
|
###*
|
216
206
|
Returns whether this browser supports the [`DOMParser`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
|
217
207
|
interface.
|
218
208
|
|
219
|
-
@function up.browser.
|
220
|
-
@return {
|
209
|
+
@function up.browser.canDOMParser
|
210
|
+
@return {boolean}
|
221
211
|
@internal
|
222
212
|
###
|
223
|
-
|
213
|
+
canDOMParser = ->
|
224
214
|
!!window.DOMParser
|
225
215
|
|
226
216
|
###*
|
227
217
|
Returns whether this browser supports the [`debugging console`](https://developer.mozilla.org/en-US/docs/Web/API/Console).
|
228
218
|
|
229
219
|
@function up.browser.canConsole
|
230
|
-
@return {
|
220
|
+
@return {boolean}
|
231
221
|
@internal
|
232
222
|
###
|
233
|
-
canConsole =
|
223
|
+
canConsole = ->
|
234
224
|
window.console &&
|
235
225
|
console.debug &&
|
236
226
|
console.info &&
|
@@ -240,7 +230,7 @@ up.browser = (($) ->
|
|
240
230
|
console.groupCollapsed &&
|
241
231
|
console.groupEnd
|
242
232
|
|
243
|
-
isRecentJQuery =
|
233
|
+
isRecentJQuery = ->
|
244
234
|
version = $.fn.jquery
|
245
235
|
parts = version.split('.')
|
246
236
|
major = parseInt(parts[0])
|
@@ -264,15 +254,15 @@ up.browser = (($) ->
|
|
264
254
|
###*
|
265
255
|
@function up,browser.whenConfirmed
|
266
256
|
@return {Promise}
|
267
|
-
@param {
|
268
|
-
@param {
|
257
|
+
@param {string} options.confirm
|
258
|
+
@param {boolean} options.preload
|
269
259
|
@internal
|
270
260
|
###
|
271
261
|
whenConfirmed = (options) ->
|
272
262
|
if options.preload || u.isBlank(options.confirm) || window.confirm(options.confirm)
|
273
|
-
|
263
|
+
Promise.resolve()
|
274
264
|
else
|
275
|
-
|
265
|
+
Promise.reject(new Error('User canceled action'))
|
276
266
|
|
277
267
|
###*
|
278
268
|
Returns whether Unpoly supports the current browser.
|
@@ -283,13 +273,6 @@ up.browser = (($) ->
|
|
283
273
|
This is usually a better fallback than loading incompatible Javascript and causing
|
284
274
|
many errors on load.
|
285
275
|
|
286
|
-
\#\#\# Graceful degradation
|
287
|
-
|
288
|
-
This function also returns `true` if Unpoly only support some features, but can degrade
|
289
|
-
gracefully for other features. E.g. Internet Explorer 9 is almost fully supported, but due to
|
290
|
-
its lack of [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
|
291
|
-
Unpoly falls back to a full page load when asked to manipulate history.
|
292
|
-
|
293
276
|
@function up.browser.isSupported
|
294
277
|
@stable
|
295
278
|
###
|
@@ -297,11 +280,13 @@ up.browser = (($) ->
|
|
297
280
|
!isIE10OrWorse() &&
|
298
281
|
isRecentJQuery() &&
|
299
282
|
canConsole() &&
|
300
|
-
|
301
|
-
|
283
|
+
# We don't require pushState in order to cater for Safari booting Unpoly with a non-GET method.
|
284
|
+
# canPushState() &&
|
285
|
+
canDOMParser() &&
|
302
286
|
canFormData() &&
|
303
287
|
canCssTransition() &&
|
304
|
-
canInputEvent()
|
288
|
+
canInputEvent() &&
|
289
|
+
canPromise()
|
305
290
|
|
306
291
|
###*
|
307
292
|
@internal
|
@@ -342,7 +327,8 @@ up.browser = (($) ->
|
|
342
327
|
|
343
328
|
knife: eval(Knife?.point)
|
344
329
|
url: url
|
345
|
-
|
330
|
+
navigate: navigate
|
331
|
+
submitForm: submitForm
|
346
332
|
canPushState: canPushState
|
347
333
|
whenConfirmed: whenConfirmed
|
348
334
|
isSupported: isSupported
|
@@ -352,5 +338,7 @@ up.browser = (($) ->
|
|
352
338
|
sessionStorage: sessionStorage
|
353
339
|
popCookie: popCookie
|
354
340
|
hash: hash
|
341
|
+
canPushState: canPushState
|
355
342
|
|
356
343
|
)(jQuery)
|
344
|
+
|
@@ -55,6 +55,9 @@ up.bus = (($) ->
|
|
55
55
|
liveUpDescriptions = {}
|
56
56
|
nextUpDescriptionNumber = 0
|
57
57
|
|
58
|
+
# A hash mapping oldEventName => newEventName
|
59
|
+
renamedEvents = {}
|
60
|
+
|
58
61
|
###*
|
59
62
|
Convert an Unpoly style listener (second argument is the event target
|
60
63
|
as a jQuery collection) to a vanilla jQuery listener
|
@@ -76,6 +79,13 @@ up.bus = (($) ->
|
|
76
79
|
###
|
77
80
|
upDescriptionToJqueryDescription = (upDescription, isNew) ->
|
78
81
|
jqueryDescription = u.copy(upDescription)
|
82
|
+
|
83
|
+
# Prefer to rename events in the copied jQuery description instead of
|
84
|
+
# changing the original up description.
|
85
|
+
fixRenamedEvents(jqueryDescription)
|
86
|
+
|
87
|
+
# We remove the listener function from the end of the description.
|
88
|
+
# We will re-push it to the description at the end.
|
79
89
|
upListener = jqueryDescription.pop()
|
80
90
|
jqueryListener = undefined
|
81
91
|
if isNew
|
@@ -84,10 +94,19 @@ up.bus = (($) ->
|
|
84
94
|
upListener._descriptionNumber = ++nextUpDescriptionNumber
|
85
95
|
else
|
86
96
|
jqueryListener = upListener._asJqueryListener
|
87
|
-
jqueryListener or up.fail('up.off: The
|
97
|
+
jqueryListener or up.fail('up.off(): The callback %o was never registered through up.on()', upListener)
|
88
98
|
jqueryDescription.push(jqueryListener)
|
89
99
|
jqueryDescription
|
90
100
|
|
101
|
+
fixRenamedEvents = (description) ->
|
102
|
+
events = description[0].split(/\s+/)
|
103
|
+
events = u.map events, (event) ->
|
104
|
+
if newEvent = renamedEvents[event]
|
105
|
+
up.log.warn("#{event} has been renamed to #{newEvent}")
|
106
|
+
newEvent
|
107
|
+
else
|
108
|
+
event
|
109
|
+
description[0] = events.join(' ')
|
91
110
|
|
92
111
|
###*
|
93
112
|
Listens to an event on `document`.
|
@@ -165,9 +184,9 @@ up.bus = (($) ->
|
|
165
184
|
});
|
166
185
|
|
167
186
|
@function up.on
|
168
|
-
@param {
|
187
|
+
@param {string} events
|
169
188
|
A space-separated list of event names to bind.
|
170
|
-
@param {
|
189
|
+
@param {string} [selector]
|
171
190
|
The selector of an element on which the event must be triggered.
|
172
191
|
Omit the selector to listen to all events with that name, regardless
|
173
192
|
of the event target.
|
@@ -251,7 +270,7 @@ up.bus = (($) ->
|
|
251
270
|
# Prints "bar" to the console
|
252
271
|
|
253
272
|
@function up.emit
|
254
|
-
@param {
|
273
|
+
@param {string} eventName
|
255
274
|
The name of the event.
|
256
275
|
@param {Object} [eventProps={}]
|
257
276
|
A list of properties to become part of the event object
|
@@ -260,7 +279,7 @@ up.bus = (($) ->
|
|
260
279
|
or `stopPropagation()`.
|
261
280
|
@param {jQuery} [eventProps.$element=$(document)]
|
262
281
|
The element on which the event is triggered.
|
263
|
-
@param {
|
282
|
+
@param {string|Array} [eventProps.message]
|
264
283
|
A message to print to the console when the event is emitted.
|
265
284
|
If omitted, a default message is printed.
|
266
285
|
Set this to `false` to prevent any console output.
|
@@ -303,40 +322,37 @@ up.bus = (($) ->
|
|
303
322
|
has prevented the default action.
|
304
323
|
|
305
324
|
@function up.bus.nobodyPrevents
|
306
|
-
@param {
|
325
|
+
@param {string} eventName
|
307
326
|
@param {Object} eventProps
|
308
|
-
@param {
|
309
|
-
@return {
|
327
|
+
@param {string|Array} [eventProps.message]
|
328
|
+
@return {boolean}
|
310
329
|
whether no listener has prevented the default action
|
311
330
|
@experimental
|
312
331
|
###
|
313
332
|
nobodyPrevents = (args...) ->
|
314
333
|
event = emit(args...)
|
315
|
-
|
316
|
-
up.puts "An observer prevented the event %s", args[0]
|
317
|
-
false
|
318
|
-
else
|
319
|
-
true
|
334
|
+
not event.isDefaultPrevented()
|
320
335
|
|
321
336
|
###*
|
322
337
|
[Emits](/up.emit) the given event and returns a promise
|
323
|
-
that will be
|
338
|
+
that will be fulfilled if no listener has prevented the default action.
|
324
339
|
|
325
340
|
If any listener prevented the default listener
|
326
341
|
the returned promise will never be resolved.
|
327
342
|
|
328
343
|
@function up.bus.whenEmitted
|
329
|
-
@param {
|
344
|
+
@param {string} eventName
|
330
345
|
@param {Object} eventProps
|
331
|
-
@param {
|
346
|
+
@param {string|Array} [eventProps.message]
|
332
347
|
@return {Promise}
|
333
348
|
@internal
|
334
349
|
###
|
335
350
|
whenEmitted = (args...) ->
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
351
|
+
new Promise (resolve, reject) ->
|
352
|
+
if nobodyPrevents(args...)
|
353
|
+
resolve()
|
354
|
+
else
|
355
|
+
reject(new Error("Event #{args[0]} was prevented"))
|
340
356
|
|
341
357
|
###*
|
342
358
|
Registers an event listener to be called when the user
|
@@ -386,18 +402,22 @@ up.bus = (($) ->
|
|
386
402
|
@internal
|
387
403
|
###
|
388
404
|
snapshot = ->
|
389
|
-
for description
|
405
|
+
for number, description of liveUpDescriptions
|
390
406
|
description.isDefault = true
|
391
407
|
|
392
|
-
|
393
|
-
|
394
|
-
|
408
|
+
resetBus = ->
|
409
|
+
# Resets the list of registered event listeners to the
|
410
|
+
# moment when the framework was booted.
|
395
411
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
doomedDescriptions =
|
400
|
-
|
412
|
+
# Copy a list of the descriptions we're going to unbind and iterate over
|
413
|
+
# them a second time below. This way we avoid manipulate the object we're
|
414
|
+
# iterating over.
|
415
|
+
doomedDescriptions = []
|
416
|
+
for number, description of liveUpDescriptions
|
417
|
+
doomedDescriptions.push(description) unless description.isDefault
|
418
|
+
|
419
|
+
for description in doomedDescriptions
|
420
|
+
unbind(description...)
|
401
421
|
|
402
422
|
###*
|
403
423
|
Resets Unpoly to the state when it was booted.
|
@@ -414,6 +434,9 @@ up.bus = (($) ->
|
|
414
434
|
###
|
415
435
|
emitReset = ->
|
416
436
|
emit('up:framework:reset', message: 'Resetting framework')
|
437
|
+
# Unfortunately we cannot reset up.protocol via event
|
438
|
+
# without introducing cycles in the asset load order
|
439
|
+
up.protocol.reset()
|
417
440
|
|
418
441
|
###*
|
419
442
|
This event is [emitted](/up.emit) when Unpoly is [reset](/up.reset) during unit tests.
|
@@ -422,6 +445,9 @@ up.bus = (($) ->
|
|
422
445
|
@experimental
|
423
446
|
###
|
424
447
|
|
448
|
+
renamedEvent = (oldEvent, newEvent) ->
|
449
|
+
renamedEvents[oldEvent] = newEvent
|
450
|
+
|
425
451
|
###*
|
426
452
|
Boots the Unpoly framework.
|
427
453
|
|
@@ -435,11 +461,9 @@ up.bus = (($) ->
|
|
435
461
|
###
|
436
462
|
boot = ->
|
437
463
|
if up.browser.isSupported()
|
438
|
-
# Can't decouple this via the event bus, since up.bus would require
|
439
|
-
# up.browser.isSupported() and up.browser would require up.on()
|
440
464
|
emit('up:framework:boot', message: 'Booting framework')
|
465
|
+
# Unpoly modules now snapshot themselves to suppot reset()
|
441
466
|
emit('up:framework:booted', message: 'Framework booted')
|
442
|
-
# User-provided compiler definitions will be registered once this function terminates.
|
443
467
|
u.nextFrame ->
|
444
468
|
# At this point all user-provided compilers have been registered.
|
445
469
|
u.whenReady().then ->
|
@@ -457,7 +481,7 @@ up.bus = (($) ->
|
|
457
481
|
###
|
458
482
|
|
459
483
|
live 'up:framework:booted', snapshot
|
460
|
-
live 'up:framework:reset',
|
484
|
+
live 'up:framework:reset', resetBus
|
461
485
|
|
462
486
|
knife: eval(Knife?.point)
|
463
487
|
on: live # can't name symbols `on` in Coffeescript
|
@@ -469,6 +493,7 @@ up.bus = (($) ->
|
|
469
493
|
emitReset: emitReset
|
470
494
|
haltEvent: haltEvent
|
471
495
|
consumeAction: consumeAction
|
496
|
+
renamedEvent: renamedEvent
|
472
497
|
boot: boot
|
473
498
|
|
474
499
|
)(jQuery)
|
@@ -0,0 +1,117 @@
|
|
1
|
+
u = up.util
|
2
|
+
|
3
|
+
###*
|
4
|
+
@class up.Cache
|
5
|
+
@internal
|
6
|
+
###
|
7
|
+
class up.Cache
|
8
|
+
|
9
|
+
###*
|
10
|
+
@constructor
|
11
|
+
@param {number|Function() :number} [config.size]
|
12
|
+
Maximum number of cache entries.
|
13
|
+
Set to `undefined` to not limit the cache size.
|
14
|
+
@param {number|Function(): number} [config.expiry]
|
15
|
+
The number of milliseconds after which a cache entry
|
16
|
+
will be discarded.
|
17
|
+
@param {string} [config.log]
|
18
|
+
A prefix for log entries printed by this cache object.
|
19
|
+
@param {Function(any): string} [config.key]
|
20
|
+
A function that takes an argument and returns a string key
|
21
|
+
for storage. If omitted, `toString()` is called on the argument.
|
22
|
+
@param {Function(any): boolean} [config.cachable]
|
23
|
+
A function that takes a potential cache entry and returns whether
|
24
|
+
this entry can be stored in the hash. If omitted, all entries are considered
|
25
|
+
cachable.
|
26
|
+
###
|
27
|
+
constructor: (@config = {}) ->
|
28
|
+
@store = {}
|
29
|
+
|
30
|
+
maxKeys: =>
|
31
|
+
u.evalOption(@config.size)
|
32
|
+
|
33
|
+
expiryMillis: =>
|
34
|
+
u.evalOption(@config.expiry)
|
35
|
+
|
36
|
+
normalizeStoreKey: (key) =>
|
37
|
+
if @config.key
|
38
|
+
@config.key(key)
|
39
|
+
else
|
40
|
+
@key.toString()
|
41
|
+
|
42
|
+
isEnabled: =>
|
43
|
+
@maxKeys() isnt 0 && @expiryMillis() isnt 0
|
44
|
+
|
45
|
+
isCachable: (key) =>
|
46
|
+
if @config.cachable
|
47
|
+
@config.cachable(key)
|
48
|
+
else
|
49
|
+
true
|
50
|
+
|
51
|
+
clear: =>
|
52
|
+
@store = {}
|
53
|
+
|
54
|
+
log: (args...) =>
|
55
|
+
if @config.logPrefix
|
56
|
+
args[0] = "[#{@config.logPrefix}] #{args[0]}"
|
57
|
+
up.puts(args...)
|
58
|
+
|
59
|
+
keys: =>
|
60
|
+
Object.keys(@store)
|
61
|
+
|
62
|
+
makeRoomForAnotherKey: =>
|
63
|
+
storeKeys = u.copy(@keys())
|
64
|
+
max = @maxKeys()
|
65
|
+
if max && storeKeys.length >= max
|
66
|
+
oldestKey = null
|
67
|
+
oldestTimestamp = null
|
68
|
+
u.each storeKeys, (key) =>
|
69
|
+
promise = @store[key] # we don't need to call cacheKey here
|
70
|
+
timestamp = promise.timestamp
|
71
|
+
if !oldestTimestamp || oldestTimestamp > timestamp
|
72
|
+
oldestKey = key
|
73
|
+
oldestTimestamp = timestamp
|
74
|
+
delete @store[oldestKey] if oldestKey
|
75
|
+
|
76
|
+
alias: (oldKey, newKey) =>
|
77
|
+
value = @get(oldKey, silent: true)
|
78
|
+
if u.isDefined(value)
|
79
|
+
@set(newKey, value)
|
80
|
+
|
81
|
+
timestamp: =>
|
82
|
+
(new Date()).valueOf()
|
83
|
+
|
84
|
+
set: (key, value) =>
|
85
|
+
if @isEnabled() && @isCachable(key)
|
86
|
+
@makeRoomForAnotherKey()
|
87
|
+
storeKey = @normalizeStoreKey(key)
|
88
|
+
@log("Setting entry %o to %o", storeKey, value)
|
89
|
+
@store[storeKey] =
|
90
|
+
timestamp: @timestamp()
|
91
|
+
value: value
|
92
|
+
|
93
|
+
remove: (key) =>
|
94
|
+
if @isCachable(key)
|
95
|
+
storeKey = @normalizeStoreKey(key)
|
96
|
+
delete @store[storeKey]
|
97
|
+
|
98
|
+
isFresh: (entry) =>
|
99
|
+
millis = @expiryMillis()
|
100
|
+
if millis
|
101
|
+
timeSinceTouch = @timestamp() - entry.timestamp
|
102
|
+
timeSinceTouch < millis
|
103
|
+
else
|
104
|
+
true
|
105
|
+
|
106
|
+
get: (key, options = {}) =>
|
107
|
+
if @isCachable(key) && (entry = @store[@normalizeStoreKey(key)])
|
108
|
+
if @isFresh(entry)
|
109
|
+
@log("Cache hit for '%s'", key) unless options.silent
|
110
|
+
entry.value
|
111
|
+
else
|
112
|
+
@log("Discarding stale cache entry for '%s'", key) unless options.silent
|
113
|
+
@remove(key)
|
114
|
+
undefined
|
115
|
+
else
|
116
|
+
@log("Cache miss for '%s'", key) unless options.silent
|
117
|
+
undefined
|
@@ -1,6 +1,6 @@
|
|
1
1
|
u = up.util
|
2
2
|
|
3
|
-
class up.
|
3
|
+
class up.ExtractCascade
|
4
4
|
|
5
5
|
constructor: (selector, options) ->
|
6
6
|
@options = u.options(options, humanizedTarget: 'selector', layer: 'auto')
|
@@ -11,7 +11,7 @@ class up.dom.ExtractCascade
|
|
11
11
|
# If we're using a fallback (any candidate that's not the first),
|
12
12
|
# the original transition might no longer be appropriate.
|
13
13
|
planOptions.transition = up.dom.config.fallbackTransition
|
14
|
-
new up.
|
14
|
+
new up.ExtractPlan(candidate, planOptions)
|
15
15
|
|
16
16
|
buildCandidates: (selector) ->
|
17
17
|
candidates = [selector, @options.fallback, up.dom.config.fallbacks]
|
@@ -63,7 +63,7 @@ class up.dom.ExtractCascade
|
|
63
63
|
message = "Could not find #{@options.humanizedTarget} in response"
|
64
64
|
else
|
65
65
|
message = "Could not match #{@options.humanizedTarget} in current page and response"
|
66
|
-
if @
|
66
|
+
if @options.inspectResponse
|
67
67
|
inspectAction = { label: 'Open response', callback: @options.inspectResponse }
|
68
68
|
up.fail(["#{message} (tried %o)", @candidates], action: inspectAction)
|
69
69
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
u = up.util
|
2
|
+
|
3
|
+
class up.FieldObserver
|
4
|
+
|
5
|
+
# Although (depending on the browser) we only need/receive either input or change,
|
6
|
+
# we always bind to both events in case another script manually triggers it.
|
7
|
+
CHANGE_EVENTS = 'input change'
|
8
|
+
|
9
|
+
constructor: (@$field, options) ->
|
10
|
+
@delay = options.delay
|
11
|
+
@callback = options.callback
|
12
|
+
|
13
|
+
start: =>
|
14
|
+
# Don't use undefined since an unchecked checkbox actually has an undefined value
|
15
|
+
@scheduledValue = null
|
16
|
+
@processedValue = @readFieldValue()
|
17
|
+
@currentTimer = undefined
|
18
|
+
@currentCallback = undefined
|
19
|
+
@$field.on(CHANGE_EVENTS, @check)
|
20
|
+
|
21
|
+
stop: =>
|
22
|
+
@$field.off(CHANGE_EVENTS, @check)
|
23
|
+
@cancelTimer()
|
24
|
+
|
25
|
+
cancelTimer: =>
|
26
|
+
clearTimeout(@currentTimer)
|
27
|
+
@currentTimer = undefined
|
28
|
+
|
29
|
+
scheduleTimer: =>
|
30
|
+
@currentTimer = u.setTimer @delay, =>
|
31
|
+
@currentTimer = undefined
|
32
|
+
@requestCallback()
|
33
|
+
|
34
|
+
isNewValue: (value) =>
|
35
|
+
value != @processedValue && (@scheduledValue == null || @scheduledValue != value)
|
36
|
+
|
37
|
+
requestCallback: =>
|
38
|
+
if @scheduledValue != null && !@currentTimer && !@currentCallback
|
39
|
+
@processedValue = @scheduledValue
|
40
|
+
@scheduledValue = null
|
41
|
+
@currentCallback = => @callback.call(@$field.get(0), @processedValue, @$field)
|
42
|
+
# If the callback returns a promise x, Promise.resolve(x) will wait for it
|
43
|
+
callbackDone = Promise.resolve(@currentCallback())
|
44
|
+
u.always callbackDone, =>
|
45
|
+
# Don't use undefined since an unchecked checkbox actually has an undefined value
|
46
|
+
@currentCallback = undefined
|
47
|
+
@requestCallback()
|
48
|
+
|
49
|
+
readFieldValue: =>
|
50
|
+
u.submittedValue(@$field)
|
51
|
+
|
52
|
+
check: =>
|
53
|
+
value = @readFieldValue()
|
54
|
+
if @isNewValue(value)
|
55
|
+
@scheduledValue = value
|
56
|
+
@cancelTimer()
|
57
|
+
@scheduleTimer()
|