unpoly-rails 0.51.1 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|