upjs-rails 0.17.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -1
- data/dist/up.js +929 -374
- data/dist/up.min.js +2 -2
- data/lib/assets/javascripts/up/browser.js.coffee +31 -14
- data/lib/assets/javascripts/up/bus.js.coffee +87 -22
- data/lib/assets/javascripts/up/flow.js.coffee +119 -43
- data/lib/assets/javascripts/up/form.js.coffee +188 -57
- data/lib/assets/javascripts/up/link.js.coffee +57 -21
- data/lib/assets/javascripts/up/modal.js.coffee +77 -63
- data/lib/assets/javascripts/up/motion.js.coffee +10 -9
- data/lib/assets/javascripts/up/popup.js.coffee +54 -40
- data/lib/assets/javascripts/up/proxy.js.coffee +46 -17
- data/lib/assets/javascripts/up/rails.js.coffee +22 -4
- data/lib/assets/javascripts/up/syntax.js.coffee +2 -2
- data/lib/assets/javascripts/up/util.js.coffee +100 -16
- data/lib/upjs/rails/inspector.rb +3 -3
- data/lib/upjs/rails/version.rb +1 -1
- data/spec_app/Gemfile.lock +1 -4
- data/spec_app/app/controllers/test_controller.rb +2 -2
- data/spec_app/spec/controllers/test_controller_spec.rb +5 -5
- data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/knife.js.coffee +0 -1
- data/spec_app/spec/javascripts/helpers/reset_path.js.coffee +4 -5
- data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +8 -0
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +26 -0
- data/spec_app/spec/javascripts/up/flow_spec.js.coffee +203 -91
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +244 -49
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +8 -2
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +83 -30
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +23 -17
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +4 -4
- data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +26 -16
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +45 -13
- data/spec_app/spec/javascripts/up/rails_spec.js.coffee +48 -0
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +48 -0
- metadata +5 -2
@@ -88,6 +88,16 @@ up.proxy = (($) ->
|
|
88
88
|
|
89
89
|
Note that your browser might [impose its own request limit](http://www.browserscope.org/?category=network)
|
90
90
|
regardless of what you configure here.
|
91
|
+
@param {Array<String>} [config.wrapMethods]
|
92
|
+
An array of uppercase HTTP method names. AJAX requests with one of these methods
|
93
|
+
will be converted into a `POST` request and carry their original method as a `_method`
|
94
|
+
parameter. This is to [prevent unexpected redirect behavior](https://makandracards.com/makandra/38347).
|
95
|
+
@param {String} [config.wrapMethodParam]
|
96
|
+
The name of the POST parameter when wrapping HTTP methods in a `POST` request.
|
97
|
+
@param {Array<String>} [config.safeMethods]
|
98
|
+
An array of uppercase HTTP method names that are considered idempotent.
|
99
|
+
The proxy cache will only cache idempotent requests and will clear the entire
|
100
|
+
cache after a non-idempotent request.
|
91
101
|
@stable
|
92
102
|
###
|
93
103
|
config = u.config
|
@@ -96,13 +106,16 @@ up.proxy = (($) ->
|
|
96
106
|
cacheSize: 70
|
97
107
|
cacheExpiry: 1000 * 60 * 5
|
98
108
|
maxRequests: 4
|
109
|
+
wrapMethods: ['PATCH', 'PUT', 'DELETE']
|
110
|
+
wrapMethodParam: '_method'
|
111
|
+
safeMethods: ['GET', 'OPTIONS', 'HEAD']
|
99
112
|
|
100
113
|
cacheKey = (request) ->
|
101
114
|
normalizeRequest(request)
|
102
115
|
[ request.url,
|
103
116
|
request.method,
|
104
117
|
request.data,
|
105
|
-
request.
|
118
|
+
request.target
|
106
119
|
].join('|')
|
107
120
|
|
108
121
|
cache = u.cache
|
@@ -125,11 +138,11 @@ up.proxy = (($) ->
|
|
125
138
|
get = (request) ->
|
126
139
|
request = normalizeRequest(request)
|
127
140
|
candidates = [request]
|
128
|
-
unless request.
|
129
|
-
requestForHtml = u.merge(request,
|
141
|
+
unless request.target is 'html'
|
142
|
+
requestForHtml = u.merge(request, target: 'html')
|
130
143
|
candidates.push(requestForHtml)
|
131
|
-
unless request.
|
132
|
-
requestForBody = u.merge(request,
|
144
|
+
unless request.target is 'body'
|
145
|
+
requestForBody = u.merge(request, target: 'body')
|
133
146
|
candidates.push(requestForBody)
|
134
147
|
for candidate in candidates
|
135
148
|
if response = cache.get(candidate)
|
@@ -141,7 +154,7 @@ up.proxy = (($) ->
|
|
141
154
|
@function up.proxy.set
|
142
155
|
@param {String} request.url
|
143
156
|
@param {String} [request.method='GET']
|
144
|
-
@param {String} [request.
|
157
|
+
@param {String} [request.target='body']
|
145
158
|
@param {Promise} response
|
146
159
|
A promise for the response that is API-compatible with the
|
147
160
|
promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
|
@@ -158,7 +171,7 @@ up.proxy = (($) ->
|
|
158
171
|
@function up.proxy.remove
|
159
172
|
@param {String} request.url
|
160
173
|
@param {String} [request.method='GET']
|
161
|
-
@param {String} [request.
|
174
|
+
@param {String} [request.target='body']
|
162
175
|
@experimental
|
163
176
|
###
|
164
177
|
remove = cache.remove
|
@@ -200,7 +213,7 @@ up.proxy = (($) ->
|
|
200
213
|
unless request._normalized
|
201
214
|
request.method = u.normalizeMethod(request.method)
|
202
215
|
request.url = u.normalizeUrl(request.url) if request.url
|
203
|
-
request.
|
216
|
+
request.target ||= 'body'
|
204
217
|
request._normalized = true
|
205
218
|
request
|
206
219
|
|
@@ -221,13 +234,15 @@ up.proxy = (($) ->
|
|
221
234
|
@function up.proxy.ajax
|
222
235
|
@param {String} request.url
|
223
236
|
@param {String} [request.method='GET']
|
224
|
-
@param {String} [request.
|
237
|
+
@param {String} [request.target='body']
|
225
238
|
@param {Boolean} [request.cache]
|
226
239
|
Whether to use a cached response, if available.
|
227
240
|
If set to `false` a network connection will always be attempted.
|
228
241
|
@param {Object} [request.headers={}]
|
229
242
|
An object of additional header key/value pairs to send along
|
230
243
|
with the request.
|
244
|
+
@param {Object} [request.data={}]
|
245
|
+
An object of request parameters.
|
231
246
|
@return
|
232
247
|
A promise for the response that is API-compatible with the
|
233
248
|
promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
|
@@ -238,7 +253,7 @@ up.proxy = (($) ->
|
|
238
253
|
forceCache = (options.cache == true)
|
239
254
|
ignoreCache = (options.cache == false)
|
240
255
|
|
241
|
-
request = u.only(options, 'url', 'method', 'data', '
|
256
|
+
request = u.only(options, 'url', 'method', 'data', 'target', 'headers', '_normalized')
|
242
257
|
|
243
258
|
pending = true
|
244
259
|
|
@@ -276,8 +291,6 @@ up.proxy = (($) ->
|
|
276
291
|
|
277
292
|
promise
|
278
293
|
|
279
|
-
SAFE_HTTP_METHODS = ['GET', 'OPTIONS', 'HEAD']
|
280
|
-
|
281
294
|
###*
|
282
295
|
Returns `true` if the proxy is not currently waiting
|
283
296
|
for a request to finish. Returns `false` otherwise.
|
@@ -372,9 +385,25 @@ up.proxy = (($) ->
|
|
372
385
|
deferred.promise()
|
373
386
|
|
374
387
|
load = (request) ->
|
375
|
-
u.debug('
|
388
|
+
u.debug('Fetching %o via %o', request.url, request.method)
|
376
389
|
up.emit('up:proxy:load', request)
|
377
|
-
|
390
|
+
|
391
|
+
# We will modify the request below for features like method wrapping.
|
392
|
+
# Let's not change the original request which would confuse API clients
|
393
|
+
# and cache key logic.
|
394
|
+
request = u.copy(request)
|
395
|
+
|
396
|
+
request.headers ||= {}
|
397
|
+
request.headers['X-Up-Target'] = request.target
|
398
|
+
request.data = u.requestDataAsArray(request.data)
|
399
|
+
|
400
|
+
if u.contains(config.wrapMethods, request.method)
|
401
|
+
request.data.push
|
402
|
+
name: config.wrapMethodParam
|
403
|
+
value: request.method
|
404
|
+
request.method = 'POST'
|
405
|
+
|
406
|
+
promise = $.ajax(request)
|
378
407
|
promise.always ->
|
379
408
|
up.emit('up:proxy:received', request)
|
380
409
|
pokeQueue()
|
@@ -393,7 +422,7 @@ up.proxy = (($) ->
|
|
393
422
|
@event up:proxy:load
|
394
423
|
@param event.url
|
395
424
|
@param event.method
|
396
|
-
@param event.
|
425
|
+
@param event.target
|
397
426
|
@experimental
|
398
427
|
###
|
399
428
|
|
@@ -404,13 +433,13 @@ up.proxy = (($) ->
|
|
404
433
|
@event up:proxy:received
|
405
434
|
@param event.url
|
406
435
|
@param event.method
|
407
|
-
@param event.
|
436
|
+
@param event.target
|
408
437
|
@experimental
|
409
438
|
###
|
410
439
|
|
411
440
|
isIdempotent = (request) ->
|
412
441
|
normalizeRequest(request)
|
413
|
-
u.contains(
|
442
|
+
u.contains(config.safeMethods, request.method)
|
414
443
|
|
415
444
|
checkPreload = ($link) ->
|
416
445
|
delay = parseInt(u.presentAttr($link, 'up-delay')) || config.preloadDelay
|
@@ -10,9 +10,27 @@ up.rails = (($) ->
|
|
10
10
|
willHandle = ($element) ->
|
11
11
|
$element.is('[up-follow], [up-target], [up-modal], [up-popup]')
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
isRails = ->
|
14
|
+
u.isGiven($.rails)
|
15
|
+
|
16
|
+
u.each ['method', 'confirm'], (feature) ->
|
17
|
+
|
18
|
+
dataAttribute = "data-#{feature}"
|
19
|
+
upAttribute = "up-#{feature}"
|
20
|
+
|
21
|
+
up.compiler "[#{dataAttribute}]", ($element) ->
|
22
|
+
if isRails() && willHandle($element)
|
23
|
+
replacement = {}
|
24
|
+
replacement[upAttribute] = $element.attr(dataAttribute)
|
25
|
+
u.setMissingAttrs($element, replacement)
|
26
|
+
$element.removeAttr(dataAttribute)
|
27
|
+
|
28
|
+
csrfField = ->
|
29
|
+
if isRails()
|
30
|
+
name: $.rails.csrfParam()
|
31
|
+
value: $.rails.csrfToken()
|
32
|
+
|
33
|
+
csrfField: csrfField
|
34
|
+
isRails: isRails
|
17
35
|
|
18
36
|
)(jQuery)
|
@@ -332,8 +332,8 @@ up.syntax = (($) ->
|
|
332
332
|
###
|
333
333
|
|
334
334
|
up.on 'ready', (-> hello(document.body))
|
335
|
-
up.on 'up:fragment:inserted', (event) -> compile(
|
336
|
-
up.on 'up:fragment:destroy', (event) -> runDestroyers(
|
335
|
+
up.on 'up:fragment:inserted', (event, $element) -> compile($element)
|
336
|
+
up.on 'up:fragment:destroy', (event, $element) -> runDestroyers($element)
|
337
337
|
up.on 'up:framework:boot', snapshot
|
338
338
|
up.on 'up:framework:reset', reset
|
339
339
|
|
@@ -23,18 +23,6 @@ up.util = (($) ->
|
|
23
23
|
cached = true
|
24
24
|
cache = func(args...)
|
25
25
|
|
26
|
-
###*
|
27
|
-
@function up.util.ajax
|
28
|
-
@internal
|
29
|
-
###
|
30
|
-
ajax = (request) ->
|
31
|
-
request = copy(request)
|
32
|
-
if request.selector
|
33
|
-
request.headers ||= {}
|
34
|
-
request.headers['X-Up-Selector'] = request.selector
|
35
|
-
# Delegate to jQuery
|
36
|
-
$.ajax(request)
|
37
|
-
|
38
26
|
###*
|
39
27
|
Returns if the given port is the default port for the given protocol.
|
40
28
|
|
@@ -247,8 +235,8 @@ up.util = (($) ->
|
|
247
235
|
selector = "##{id}"
|
248
236
|
else if name = presence($element.attr("name"))
|
249
237
|
selector = "[name='#{name}']"
|
250
|
-
else if
|
251
|
-
|
238
|
+
else if classes = presence(nonUpClasses($element))
|
239
|
+
console.log("using klass!", classes)
|
252
240
|
selector = ''
|
253
241
|
for klass in classes
|
254
242
|
selector += ".#{klass}"
|
@@ -256,6 +244,11 @@ up.util = (($) ->
|
|
256
244
|
selector = $element.prop('tagName').toLowerCase()
|
257
245
|
selector
|
258
246
|
|
247
|
+
nonUpClasses = ($element) ->
|
248
|
+
classString = $element.attr('class') || ''
|
249
|
+
classes = classString.split(' ')
|
250
|
+
select classes, (klass) -> isPresent(klass) && !klass.match(/^up-/)
|
251
|
+
|
259
252
|
# jQuery's implementation of $(...) cannot create elements that have
|
260
253
|
# an <html> or <body> tag. So we're using native elements.
|
261
254
|
# Also IE9 cannot set innerHTML on a <html> or <head> element.
|
@@ -761,6 +754,30 @@ up.util = (($) ->
|
|
761
754
|
matches.push(element)
|
762
755
|
matches
|
763
756
|
|
757
|
+
###*
|
758
|
+
Returns all elements from the given array that do not return
|
759
|
+
a truthy value when passed to the given function.
|
760
|
+
|
761
|
+
@function up.util.reject
|
762
|
+
@param {Array<T>} array
|
763
|
+
@return {Array<T>}
|
764
|
+
@stable
|
765
|
+
###
|
766
|
+
reject = (array, tester) ->
|
767
|
+
select(array, (element) -> !tester(element))
|
768
|
+
|
769
|
+
###*
|
770
|
+
Returns the intersection of the given two arrays.
|
771
|
+
|
772
|
+
Implementation is not optimized. Don't use it for large arrays.
|
773
|
+
|
774
|
+
@function up.util.intersect
|
775
|
+
@internal
|
776
|
+
###
|
777
|
+
intersect = (array1, array2) ->
|
778
|
+
select array1, (element) ->
|
779
|
+
contains(array2, element)
|
780
|
+
|
764
781
|
###*
|
765
782
|
Returns the first [present](/up.util.isPresent) element attribute
|
766
783
|
among the given list of attribute names.
|
@@ -1098,6 +1115,21 @@ up.util = (($) ->
|
|
1098
1115
|
filtered[property] = object[property]
|
1099
1116
|
filtered
|
1100
1117
|
|
1118
|
+
###*
|
1119
|
+
Returns a copy of the given object that contains all except
|
1120
|
+
the given properties.
|
1121
|
+
|
1122
|
+
@function up.util.except
|
1123
|
+
@param {Object} object
|
1124
|
+
@param {Array} keys...
|
1125
|
+
@stable
|
1126
|
+
###
|
1127
|
+
except = (object, properties...) ->
|
1128
|
+
filtered = copy(object)
|
1129
|
+
for property in properties
|
1130
|
+
delete filtered[property]
|
1131
|
+
filtered
|
1132
|
+
|
1101
1133
|
###*
|
1102
1134
|
@function up.util.isUnmodifiedKeyEvent
|
1103
1135
|
@internal
|
@@ -1179,7 +1211,20 @@ up.util = (($) ->
|
|
1179
1211
|
joined.resolve = ->
|
1180
1212
|
each deferreds, (deferred) -> deferred.resolve?()
|
1181
1213
|
joined
|
1182
|
-
|
1214
|
+
|
1215
|
+
# resolvableSequence = (first, callbacks...) ->
|
1216
|
+
# sequence = $.Deferred().promise()
|
1217
|
+
# values = [first]
|
1218
|
+
# current = first
|
1219
|
+
# for callback in callbacks
|
1220
|
+
# current = current.then ->
|
1221
|
+
# value = callback()
|
1222
|
+
# values.push(value) if u.isPromise(value)
|
1223
|
+
# value
|
1224
|
+
# sequence.resolve = ->
|
1225
|
+
# each values, (deferred) -> deferred.resolve?()
|
1226
|
+
# sequence
|
1227
|
+
|
1183
1228
|
###*
|
1184
1229
|
On the given element, set attributes that are still missing.
|
1185
1230
|
|
@@ -1446,6 +1491,43 @@ up.util = (($) ->
|
|
1446
1491
|
# else
|
1447
1492
|
# error('Could not parse argument names of %o', fun)
|
1448
1493
|
|
1494
|
+
###*
|
1495
|
+
Normalizes the given params object to the form returned by
|
1496
|
+
[`jQuery.serializeArray`](https://api.jquery.com/serializeArray/).
|
1497
|
+
|
1498
|
+
@function up.util.requestDataAsArray
|
1499
|
+
@param {Object|Array|Undefined|Null} data
|
1500
|
+
@internal
|
1501
|
+
###
|
1502
|
+
requestDataAsArray = (data) ->
|
1503
|
+
if isMissing(data)
|
1504
|
+
[]
|
1505
|
+
else if isArray(data)
|
1506
|
+
data
|
1507
|
+
else if isObject(data)
|
1508
|
+
{ name: name, value: value } for name, value of data
|
1509
|
+
else
|
1510
|
+
error('Unknown options.data type for %o', data)
|
1511
|
+
|
1512
|
+
###*
|
1513
|
+
Returns an URL-encoded query string for the given params object.
|
1514
|
+
|
1515
|
+
@function up.util.requestDataAsQueryString
|
1516
|
+
@param {Object|Array|Undefined|Null} data
|
1517
|
+
@internal
|
1518
|
+
###
|
1519
|
+
requestDataAsQueryString = (data) ->
|
1520
|
+
array = requestDataAsArray(data)
|
1521
|
+
query = ''
|
1522
|
+
if isPresent(array)
|
1523
|
+
query += '?'
|
1524
|
+
each array, (field, index) ->
|
1525
|
+
query += '&' unless index == 0
|
1526
|
+
query += encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value)
|
1527
|
+
query
|
1528
|
+
|
1529
|
+
requestDataAsArray: requestDataAsArray
|
1530
|
+
requestDataAsQueryString: requestDataAsQueryString
|
1449
1531
|
offsetParent: offsetParent
|
1450
1532
|
fixedToAbsolute: fixedToAbsolute
|
1451
1533
|
presentAttr: presentAttr
|
@@ -1456,7 +1538,6 @@ up.util = (($) ->
|
|
1456
1538
|
createElementFromHtml: createElementFromHtml
|
1457
1539
|
$createElementFromSelector: $createElementFromSelector
|
1458
1540
|
selectorForElement: selectorForElement
|
1459
|
-
ajax: ajax
|
1460
1541
|
extend: extend
|
1461
1542
|
copy: copy
|
1462
1543
|
merge: merge
|
@@ -1471,6 +1552,8 @@ up.util = (($) ->
|
|
1471
1552
|
any: any
|
1472
1553
|
detect: detect
|
1473
1554
|
select: select
|
1555
|
+
reject: reject
|
1556
|
+
intersect: intersect
|
1474
1557
|
compact: compact
|
1475
1558
|
uniq: uniq
|
1476
1559
|
last: last
|
@@ -1514,6 +1597,7 @@ up.util = (($) ->
|
|
1514
1597
|
methodFromXhr: methodFromXhr
|
1515
1598
|
clientSize: clientSize
|
1516
1599
|
only: only
|
1600
|
+
except: except
|
1517
1601
|
trim: trim
|
1518
1602
|
unresolvableDeferred: unresolvableDeferred
|
1519
1603
|
unresolvablePromise: unresolvablePromise
|
data/lib/upjs/rails/inspector.rb
CHANGED
@@ -16,7 +16,7 @@ module Upjs
|
|
16
16
|
# [page fragment update](http://upjs.io/up.replace) triggered by an
|
17
17
|
# Up.js frontend.
|
18
18
|
def up?
|
19
|
-
|
19
|
+
target.present?
|
20
20
|
end
|
21
21
|
|
22
22
|
##
|
@@ -29,8 +29,8 @@ module Upjs
|
|
29
29
|
#
|
30
30
|
# Server-side code is free to optimize its response by only returning HTML
|
31
31
|
# that matches this selector.
|
32
|
-
def
|
33
|
-
request.headers['X-Up-
|
32
|
+
def target
|
33
|
+
request.headers['X-Up-Target']
|
34
34
|
end
|
35
35
|
|
36
36
|
##
|
data/lib/upjs/rails/version.rb
CHANGED
data/spec_app/Gemfile.lock
CHANGED
@@ -2,13 +2,13 @@ describe TestController do
|
|
2
2
|
|
3
3
|
describe '#up?' do
|
4
4
|
|
5
|
-
it 'returns true if the request has an X-Up-
|
6
|
-
request.headers['X-Up-
|
5
|
+
it 'returns true if the request has an X-Up-Target header' do
|
6
|
+
request.headers['X-Up-Target'] = 'body'
|
7
7
|
get :is_up
|
8
8
|
expect(response.body).to eq('true')
|
9
9
|
end
|
10
10
|
|
11
|
-
it 'returns false if the request has no X-Up-
|
11
|
+
it 'returns false if the request has no X-Up-Target header' do
|
12
12
|
get :is_up
|
13
13
|
expect(response.body).to eq('false')
|
14
14
|
end
|
@@ -20,8 +20,8 @@ describe TestController do
|
|
20
20
|
describe '#selector' do
|
21
21
|
|
22
22
|
it 'returns the CSS selector that is requested via Up.js' do
|
23
|
-
request.headers['X-Up-
|
24
|
-
get :
|
23
|
+
request.headers['X-Up-Target'] = '.foo'
|
24
|
+
get :up_target
|
25
25
|
expect(response.body).to eq('.foo')
|
26
26
|
end
|
27
27
|
|