unpoly-rails 0.51.1 → 0.52.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 +21 -2
- data/dist/unpoly.js +102 -33
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +3 -2
- data/lib/assets/javascripts/unpoly/classes/request.coffee +20 -7
- data/lib/assets/javascripts/unpoly/form.coffee +23 -9
- data/lib/assets/javascripts/unpoly/link.coffee +13 -0
- data/lib/assets/javascripts/unpoly/protocol.coffee +5 -7
- data/lib/assets/javascripts/unpoly/proxy.coffee +1 -1
- data/lib/assets/javascripts/unpoly/syntax.coffee +6 -0
- data/lib/assets/javascripts/unpoly/util.coffee +16 -2
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile.lock +1 -1
- data/spec_app/app/controllers/form_test/redirects_controller.rb +17 -0
- data/spec_app/app/views/form_test/redirects/new.erb +27 -0
- data/spec_app/app/views/form_test/redirects/target.erb +4 -0
- data/spec_app/app/views/pages/start.erb +1 -0
- data/spec_app/config/routes.rb +5 -0
- data/spec_app/spec/javascripts/helpers/last_request.js.coffee +2 -0
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +19 -0
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +18 -0
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +18 -0
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +59 -0
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +8 -0
- metadata +5 -2
@@ -60,6 +60,8 @@ up.form = (($) ->
|
|
60
60
|
See the documentation for [`form[up-target]`](/form-up-target) for more
|
61
61
|
information on how AJAX form submissions work in Unpoly.
|
62
62
|
|
63
|
+
Emits the event [`up:form:submit`](/up:form:submit).
|
64
|
+
|
63
65
|
@function up.submit
|
64
66
|
@param {Element|jQuery|string} formOrSelector
|
65
67
|
A reference or selector for the form to submit.
|
@@ -154,17 +156,29 @@ up.form = (($) ->
|
|
154
156
|
options.failTransition = false
|
155
157
|
options.headers[up.protocol.config.validateHeader] = options.validate
|
156
158
|
|
157
|
-
up.
|
159
|
+
up.bus.whenEmitted('up:form:submit', $element: $form).then ->
|
160
|
+
up.feedback.start($form)
|
158
161
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
162
|
+
# If we can't update the location URL, fall back to a vanilla form submission.
|
163
|
+
unless up.browser.canPushState() || options.history == false
|
164
|
+
# Don't use up.browser.navigate(); It cannot deal with file inputs.
|
165
|
+
$form.get(0).submit()
|
166
|
+
return u.unresolvablePromise()
|
164
167
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
+
promise = up.replace(target, url, options)
|
169
|
+
u.always promise, -> up.feedback.stop($form)
|
170
|
+
promise
|
171
|
+
|
172
|
+
###*
|
173
|
+
This event is [emitted](/up.emit) when a form is [submitted](/up.submit) through Unpoly.
|
174
|
+
|
175
|
+
@event up:form:submit
|
176
|
+
@param {jQuery} event.$element
|
177
|
+
The `<form>` element that will be submitted.
|
178
|
+
@param event.preventDefault()
|
179
|
+
Event listeners may call this method to prevent the form from being submitted.
|
180
|
+
@stable
|
181
|
+
###
|
168
182
|
|
169
183
|
###*
|
170
184
|
Observes form fields and runs a callback when a value changes.
|
@@ -103,6 +103,8 @@ up.link = (($) ->
|
|
103
103
|
or [`[up-modal]`](/a-up-modal), the corresponding UJS behavior will be activated
|
104
104
|
just as if the user had clicked on the link.
|
105
105
|
|
106
|
+
Emits the event [`up:link:follow`](/up:link:follow).
|
107
|
+
|
106
108
|
\#\#\# Examples
|
107
109
|
|
108
110
|
Let's say you have a link with an [`a[up-target]`](/a-up-target) attribute:
|
@@ -133,6 +135,17 @@ up.link = (($) ->
|
|
133
135
|
variant = followVariantForLink($link)
|
134
136
|
variant.followLink($link, options)
|
135
137
|
|
138
|
+
###*
|
139
|
+
This event is [emitted](/up.emit) when a link is [followed](/up.follow) through Unpoly.
|
140
|
+
|
141
|
+
@event up:link:follow
|
142
|
+
@param {jQuery} event.$element
|
143
|
+
The link element that will be followed.
|
144
|
+
@param event.preventDefault()
|
145
|
+
Event listeners may call this method to prevent the link from being followed.
|
146
|
+
@stable
|
147
|
+
###
|
148
|
+
|
136
149
|
###*
|
137
150
|
@function defaultFollow
|
138
151
|
@internal
|
@@ -22,13 +22,11 @@ in your controllers and views. If your server-side app uses another language
|
|
22
22
|
or framework, you should be able to implement the protocol in a very short time.
|
23
23
|
|
24
24
|
|
25
|
-
\#\#\# Redirect detection
|
25
|
+
\#\#\# Redirect detection for IE11
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
After the form's action performs a redirect, the next response should include the new
|
31
|
-
URL in the HTTP headers:
|
27
|
+
On Internet Explorer 11, Unpoly cannot detect the final URL after a redirect.
|
28
|
+
You can fix this edge case by delivering an additional HTTP header
|
29
|
+
with the *last* response in a series of redirects:
|
32
30
|
|
33
31
|
```http
|
34
32
|
X-Up-Location: /current-url
|
@@ -179,7 +177,7 @@ up.protocol = (($) ->
|
|
179
177
|
@internal
|
180
178
|
###
|
181
179
|
locationFromXhr = (xhr) ->
|
182
|
-
xhr.getResponseHeader(config.locationHeader)
|
180
|
+
xhr.getResponseHeader(config.locationHeader) || xhr.responseURL
|
183
181
|
|
184
182
|
###*
|
185
183
|
@function up.protocol.titleFromXhr
|
@@ -459,7 +459,7 @@ up.proxy = (($) ->
|
|
459
459
|
|
460
460
|
registerAliasForRedirect = (response) ->
|
461
461
|
request = response.request
|
462
|
-
if request.url != response.url
|
462
|
+
if response.url && request.url != response.url
|
463
463
|
newRequest = request.copy(
|
464
464
|
method: response.method
|
465
465
|
url: response.url
|
@@ -218,6 +218,9 @@ up.syntax = (($) ->
|
|
218
218
|
@stable
|
219
219
|
###
|
220
220
|
compiler = (selector, args...) ->
|
221
|
+
# Developer might still call top-level compiler registrations even when we don't boot
|
222
|
+
# due to an unsupported browser. In that case do no work and exit early.
|
223
|
+
return unless up.browser.isSupported()
|
221
224
|
callback = args.pop()
|
222
225
|
options = u.options(args[0])
|
223
226
|
insertCompiler(compilers, selector, options, callback)
|
@@ -264,6 +267,9 @@ up.syntax = (($) ->
|
|
264
267
|
@stable
|
265
268
|
###
|
266
269
|
macro = (selector, args...) ->
|
270
|
+
# Developer might still call top-level compiler registrations even when we don't boot
|
271
|
+
# due to an unsupported browser. In that case do no work and exit early.
|
272
|
+
return unless up.browser.isSupported()
|
267
273
|
callback = args.pop()
|
268
274
|
options = u.options(args[0])
|
269
275
|
if isBooting
|
@@ -1330,9 +1330,10 @@ up.util = (($) ->
|
|
1330
1330
|
###
|
1331
1331
|
requestDataAsQuery = (data, opts) ->
|
1332
1332
|
opts = options(opts, purpose: 'url')
|
1333
|
+
|
1333
1334
|
if isString(data)
|
1334
|
-
data
|
1335
|
-
if isFormData(data)
|
1335
|
+
data.replace(/^\?/, '')
|
1336
|
+
else if isFormData(data)
|
1336
1337
|
# Until FormData#entries is implemented in all major browsers we must give up here.
|
1337
1338
|
# However, up.form will prefer to serialize forms as arrays, so we should be good
|
1338
1339
|
# in most cases. We only use FormData for forms with file inputs.
|
@@ -1409,6 +1410,18 @@ up.util = (($) ->
|
|
1409
1410
|
data = [data, newPair].join('&')
|
1410
1411
|
data
|
1411
1412
|
|
1413
|
+
###*
|
1414
|
+
Merges the request data in `source` into `target`.
|
1415
|
+
Will modify the passed-in `target`.
|
1416
|
+
|
1417
|
+
@return
|
1418
|
+
The merged form data.
|
1419
|
+
###
|
1420
|
+
mergeRequestData = (target, source) ->
|
1421
|
+
each requestDataAsArray(source), (field) ->
|
1422
|
+
target = appendRequestData(target, field.name, field.value)
|
1423
|
+
target
|
1424
|
+
|
1412
1425
|
###*
|
1413
1426
|
Throws a [JavaScript error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
|
1414
1427
|
with the given message.
|
@@ -1729,6 +1742,7 @@ up.util = (($) ->
|
|
1729
1742
|
requestDataAsArray: requestDataAsArray
|
1730
1743
|
requestDataAsQuery: requestDataAsQuery
|
1731
1744
|
appendRequestData: appendRequestData
|
1745
|
+
mergeRequestData: mergeRequestData
|
1732
1746
|
requestDataFromForm: requestDataFromForm
|
1733
1747
|
offsetParent: offsetParent
|
1734
1748
|
fixedToAbsolute: fixedToAbsolute
|
data/lib/unpoly/rails/version.rb
CHANGED
data/package.json
CHANGED
data/spec_app/Gemfile.lock
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
<div class="example">
|
2
|
+
|
3
|
+
<h2>Redirecting form</h2>
|
4
|
+
|
5
|
+
<%= form_tag "/form_test/redirect", method: :post, 'up-target' => '.result' do %>
|
6
|
+
|
7
|
+
<p>
|
8
|
+
<input type="text" name="text-param" value="text-value" />
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<input type="password" name="password-param" value="password-value" />
|
13
|
+
</p>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
<select name="select-param">
|
17
|
+
<option value="select-value">select-label</option>
|
18
|
+
</select>
|
19
|
+
</p>
|
20
|
+
|
21
|
+
<p>
|
22
|
+
<button type="submit">Submit</button>
|
23
|
+
</p>
|
24
|
+
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
</div>
|
@@ -56,6 +56,7 @@
|
|
56
56
|
</li>
|
57
57
|
<li><%= link_to 'Form (basic)', '/form_test/basic/new' %></li>
|
58
58
|
<li><%= link_to 'Form (upload)', '/form_test/upload/new' %></li>
|
59
|
+
<li><%= link_to 'Form (redirect)', '/form_test/redirect/new' %></li>
|
59
60
|
<li><%= link_to 'Error', '/error_test/trigger' %></li>
|
60
61
|
<li><%= link_to 'Booting with non-GET method', '/method_test/page1' %></li>
|
61
62
|
<li><%= link_to 'Fragment update', '/replace_test/page1' %></li>
|
data/spec_app/config/routes.rb
CHANGED
@@ -23,6 +23,14 @@ describe 'up.browser', ->
|
|
23
23
|
# No params should be left in the form
|
24
24
|
expect($form.find('input')).not.toExist()
|
25
25
|
|
26
|
+
it 'merges params from the given URL and the { data } option', ->
|
27
|
+
submitForm = spyOn(up.browser, 'submitForm')
|
28
|
+
up.browser.navigate('/foo?param1=param1%20value', method: 'GET', data: { param2: 'param2 value' })
|
29
|
+
expect(submitForm).toHaveBeenCalled()
|
30
|
+
$form = $('form.up-page-loader')
|
31
|
+
expect($form).toExist()
|
32
|
+
expect($form.attr('action')).toMatchUrl('/foo?param1=param1%20value¶m2=param2%20value')
|
33
|
+
|
26
34
|
describe "for POST requests", ->
|
27
35
|
|
28
36
|
it "creates a POST form, adds all { data } params a hidden fields and submits the form", ->
|
@@ -36,6 +44,17 @@ describe 'up.browser', ->
|
|
36
44
|
expect($form.find('input[name="param1"][value="param1 value"]')).toExist()
|
37
45
|
expect($form.find('input[name="param2"][value="param2 value"]')).toExist()
|
38
46
|
|
47
|
+
it 'merges params from the given URL and the { data } option', ->
|
48
|
+
submitForm = spyOn(up.browser, 'submitForm')
|
49
|
+
up.browser.navigate('/foo?param1=param1%20value', method: 'POST', data: { param2: 'param2 value' })
|
50
|
+
expect(submitForm).toHaveBeenCalled()
|
51
|
+
$form = $('form.up-page-loader')
|
52
|
+
expect($form).toExist()
|
53
|
+
expect($form.attr('action')).toMatchUrl('/foo')
|
54
|
+
expect($form.attr('method')).toEqual('POST')
|
55
|
+
expect($form.find('input[name="param1"][value="param1 value"]')).toExist()
|
56
|
+
expect($form.find('input[name="param2"][value="param2 value"]')).toExist()
|
57
|
+
|
39
58
|
u.each ['PUT', 'PATCH', 'DELETE'], (method) ->
|
40
59
|
|
41
60
|
describe "for #{method} requests", ->
|
@@ -263,6 +263,24 @@ describe 'up.form', ->
|
|
263
263
|
|
264
264
|
describe 'up.submit', ->
|
265
265
|
|
266
|
+
it 'emits a preventable up:form:submit event', asyncSpec (next) ->
|
267
|
+
$form = affix('form[action="/form-target"][up-target=".response"]')
|
268
|
+
|
269
|
+
listener = jasmine.createSpy('submit listener').and.callFake (event) ->
|
270
|
+
event.preventDefault()
|
271
|
+
|
272
|
+
$form.on('up:form:submit', listener)
|
273
|
+
|
274
|
+
up.submit($form)
|
275
|
+
|
276
|
+
next =>
|
277
|
+
expect(listener).toHaveBeenCalled()
|
278
|
+
event = listener.calls.mostRecent().args[0]
|
279
|
+
expect(event.$element).toEqual($form)
|
280
|
+
|
281
|
+
# No request should be made because we prevented the event
|
282
|
+
expect(jasmine.Ajax.requests.count()).toEqual(0)
|
283
|
+
|
266
284
|
describeCapability 'canPushState', ->
|
267
285
|
|
268
286
|
beforeEach ->
|
@@ -6,6 +6,24 @@ describe 'up.link', ->
|
|
6
6
|
|
7
7
|
describe 'up.follow', ->
|
8
8
|
|
9
|
+
it 'emits a preventable up:link:follow event', asyncSpec (next) ->
|
10
|
+
$link = affix('a[href="/destination"][up-target=".response"]')
|
11
|
+
|
12
|
+
listener = jasmine.createSpy('follow listener').and.callFake (event) ->
|
13
|
+
event.preventDefault()
|
14
|
+
|
15
|
+
$link.on('up:link:follow', listener)
|
16
|
+
|
17
|
+
up.follow($link)
|
18
|
+
|
19
|
+
next =>
|
20
|
+
expect(listener).toHaveBeenCalled()
|
21
|
+
event = listener.calls.mostRecent().args[0]
|
22
|
+
expect(event.$element).toEqual($link)
|
23
|
+
|
24
|
+
# No request should be made because we prevented the event
|
25
|
+
expect(jasmine.Ajax.requests.count()).toEqual(0)
|
26
|
+
|
9
27
|
describeCapability 'canPushState', ->
|
10
28
|
|
11
29
|
it 'loads the given link via AJAX and replaces the response in the given target', asyncSpec (next) ->
|
@@ -182,6 +182,65 @@ describe 'up.proxy', ->
|
|
182
182
|
# See that the promise was not rejected due to an internal error.
|
183
183
|
expect(result.state).toEqual('pending')
|
184
184
|
|
185
|
+
|
186
|
+
describe 'when the XHR object has a { responseURL } property', ->
|
187
|
+
|
188
|
+
it 'sets the { url } property on the response object', (done) ->
|
189
|
+
promise = up.request('/request-url#request-hash')
|
190
|
+
|
191
|
+
u.nextFrame =>
|
192
|
+
@respondWith
|
193
|
+
responseURL: '/response-url'
|
194
|
+
|
195
|
+
promise.then (response) ->
|
196
|
+
expect(response.request.url).toMatchUrl('/request-url')
|
197
|
+
expect(response.request.hash).toEqual('#request-hash')
|
198
|
+
expect(response.url).toMatchUrl('/response-url')
|
199
|
+
done()
|
200
|
+
|
201
|
+
it 'considers a redirection URL an alias for the requested URL', asyncSpec (next) ->
|
202
|
+
up.request('/foo')
|
203
|
+
|
204
|
+
next =>
|
205
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
206
|
+
@respondWith
|
207
|
+
responseURL: '/bar'
|
208
|
+
|
209
|
+
next =>
|
210
|
+
up.request('/bar')
|
211
|
+
|
212
|
+
next =>
|
213
|
+
# See that the cached alias is used and no additional requests are made
|
214
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
215
|
+
|
216
|
+
it 'does not considers a redirection URL an alias for the requested URL if the original request was never cached', asyncSpec (next) ->
|
217
|
+
up.request('/foo', method: 'post') # POST requests are not cached
|
218
|
+
|
219
|
+
next =>
|
220
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
221
|
+
@respondWith
|
222
|
+
responseURL: '/bar'
|
223
|
+
|
224
|
+
next =>
|
225
|
+
up.request('/bar')
|
226
|
+
|
227
|
+
next =>
|
228
|
+
# See that an additional request was made
|
229
|
+
expect(jasmine.Ajax.requests.count()).toEqual(2)
|
230
|
+
|
231
|
+
it 'does not considers a redirection URL an alias for the requested URL if the response returned a non-200 status code', asyncSpec (next) ->
|
232
|
+
up.request('/foo')
|
233
|
+
|
234
|
+
next =>
|
235
|
+
expect(jasmine.Ajax.requests.count()).toEqual(1)
|
236
|
+
@respondWith
|
237
|
+
responseURL: '/bar'
|
238
|
+
status: 500
|
239
|
+
|
240
|
+
next =>
|
241
|
+
up.request('/bar')
|
242
|
+
|
243
|
+
|
185
244
|
describe 'CSRF', ->
|
186
245
|
|
187
246
|
beforeEach ->
|
@@ -485,6 +485,14 @@ describe 'up.util', ->
|
|
485
485
|
])
|
486
486
|
expect(string).toEqual("foo-key=foo#{encodedSpace}value&bar-key=bar#{encodedSpace}value")
|
487
487
|
|
488
|
+
it 'returns a given query string', ->
|
489
|
+
string = up.util.requestDataAsQuery('foo=bar')
|
490
|
+
expect(string).toEqual('foo=bar')
|
491
|
+
|
492
|
+
it 'strips a leading question mark from the given query string', ->
|
493
|
+
string = up.util.requestDataAsQuery('?foo=bar')
|
494
|
+
expect(string).toEqual('foo=bar')
|
495
|
+
|
488
496
|
it 'returns an empty string for an empty object', ->
|
489
497
|
string = up.util.requestDataAsQuery({})
|
490
498
|
expect(string).toEqual('')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unpoly-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.52.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- spec_app/app/controllers/css_test_controller.rb
|
163
163
|
- spec_app/app/controllers/error_test_controller.rb
|
164
164
|
- spec_app/app/controllers/form_test/basics_controller.rb
|
165
|
+
- spec_app/app/controllers/form_test/redirects_controller.rb
|
165
166
|
- spec_app/app/controllers/form_test/uploads_controller.rb
|
166
167
|
- spec_app/app/controllers/method_test_controller.rb
|
167
168
|
- spec_app/app/controllers/pages_controller.rb
|
@@ -178,6 +179,8 @@ files:
|
|
178
179
|
- spec_app/app/views/error_test/trigger.erb
|
179
180
|
- spec_app/app/views/error_test/unexpected_response.erb
|
180
181
|
- spec_app/app/views/form_test/basics/new.erb
|
182
|
+
- spec_app/app/views/form_test/redirects/new.erb
|
183
|
+
- spec_app/app/views/form_test/redirects/target.erb
|
181
184
|
- spec_app/app/views/form_test/submission_result.erb
|
182
185
|
- spec_app/app/views/form_test/uploads/new.erb
|
183
186
|
- spec_app/app/views/layouts/integration_test.erb
|