unpoly-rails 0.22.0 → 0.22.1
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 +14 -0
- data/README_RAILS.md +1 -1
- data/dist/unpoly.js +271 -157
- data/dist/unpoly.min.js +2 -2
- data/lib/assets/javascripts/unpoly/bus.js.coffee +52 -26
- data/lib/assets/javascripts/unpoly/flow.js.coffee +10 -5
- data/lib/assets/javascripts/unpoly/history.js.coffee +1 -0
- data/lib/assets/javascripts/unpoly/layout.js.coffee +17 -5
- data/lib/assets/javascripts/unpoly/link.js.coffee +60 -61
- data/lib/assets/javascripts/unpoly/log.js.coffee +8 -0
- data/lib/assets/javascripts/unpoly/motion.js.coffee +20 -9
- data/lib/assets/javascripts/unpoly/proxy.js.coffee +42 -37
- data/lib/assets/javascripts/unpoly/syntax.js.coffee +46 -7
- data/lib/assets/javascripts/unpoly/util.js.coffee +4 -3
- data/lib/unpoly/rails/version.rb +1 -1
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +104 -9
- metadata +2 -2
@@ -5,7 +5,7 @@ Caching and preloading
|
|
5
5
|
All HTTP requests go through the Unpoly proxy.
|
6
6
|
It caches a [limited](/up.proxy.config) number of server responses
|
7
7
|
for a [limited](/up.proxy.config) amount of time,
|
8
|
-
making requests to these URLs return
|
8
|
+
making requests to these URLs return instantly.
|
9
9
|
|
10
10
|
The cache is cleared whenever the user makes a non-`GET` request
|
11
11
|
(like `POST`, `PUT` or `DELETE`).
|
@@ -15,42 +15,6 @@ links when the user hovers over the click area](/up-preload) (or puts the mouse/
|
|
15
15
|
down before releasing). This way the response will already be cached when
|
16
16
|
the user performs the click.
|
17
17
|
|
18
|
-
Spinners
|
19
|
-
--------
|
20
|
-
|
21
|
-
You can [listen](/up.on) to the [`up:proxy:slow`](/up:proxy:slow)
|
22
|
-
and [`up:proxy:recover`](/up:proxy:recover) events to implement a spinner
|
23
|
-
that appears during a long-running request,
|
24
|
-
and disappears once the response has been received:
|
25
|
-
|
26
|
-
<div class="spinner">Please wait!</div>
|
27
|
-
|
28
|
-
Here is the Javascript to make it alive:
|
29
|
-
|
30
|
-
up.compiler('.spinner', function($element) {
|
31
|
-
|
32
|
-
show = function() { $element.show() };
|
33
|
-
hide = function() { $element.hide() };
|
34
|
-
|
35
|
-
showOff = up.on('up:proxy:slow', show);
|
36
|
-
hideOff = up.on('up:proxy:recover', hide);
|
37
|
-
|
38
|
-
hide();
|
39
|
-
|
40
|
-
// Clean up when the element is removed from the DOM
|
41
|
-
return function() {
|
42
|
-
showOff();
|
43
|
-
hideOff();
|
44
|
-
};
|
45
|
-
|
46
|
-
});
|
47
|
-
|
48
|
-
The `up:proxy:slow` event will be emitted after a delay of 300 ms
|
49
|
-
to prevent the spinner from flickering on and off.
|
50
|
-
You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.config) like this:
|
51
|
-
|
52
|
-
up.proxy.config.slowDelay = 150;
|
53
|
-
|
54
18
|
@class up.proxy
|
55
19
|
###
|
56
20
|
up.proxy = (($) ->
|
@@ -349,6 +313,43 @@ up.proxy = (($) ->
|
|
349
313
|
Note that if additional requests are made while Unpoly is already busy
|
350
314
|
waiting, **no** additional `up:proxy:slow` events will be triggered.
|
351
315
|
|
316
|
+
|
317
|
+
\#\#\#\# Spinners
|
318
|
+
|
319
|
+
You can [listen](/up.on) to the `up:proxy:slow`
|
320
|
+
and [`up:proxy:recover`](/up:proxy:recover) events to implement a spinner
|
321
|
+
that appears during a long-running request,
|
322
|
+
and disappears once the response has been received:
|
323
|
+
|
324
|
+
<div class="spinner">Please wait!</div>
|
325
|
+
|
326
|
+
Here is the Javascript to make it alive:
|
327
|
+
|
328
|
+
up.compiler('.spinner', function($element) {
|
329
|
+
|
330
|
+
show = function() { $element.show() };
|
331
|
+
hide = function() { $element.hide() };
|
332
|
+
|
333
|
+
showOff = up.on('up:proxy:slow', show);
|
334
|
+
hideOff = up.on('up:proxy:recover', hide);
|
335
|
+
|
336
|
+
hide();
|
337
|
+
|
338
|
+
// Clean up when the element is removed from the DOM
|
339
|
+
return function() {
|
340
|
+
showOff();
|
341
|
+
hideOff();
|
342
|
+
};
|
343
|
+
|
344
|
+
});
|
345
|
+
|
346
|
+
The `up:proxy:slow` event will be emitted after a delay of 300 ms
|
347
|
+
to prevent the spinner from flickering on and off.
|
348
|
+
You can change (or remove) this delay by [configuring `up.proxy`](/up.proxy.config) like this:
|
349
|
+
|
350
|
+
up.proxy.config.slowDelay = 150;
|
351
|
+
|
352
|
+
|
352
353
|
@event up:proxy:slow
|
353
354
|
@stable
|
354
355
|
###
|
@@ -363,6 +364,10 @@ up.proxy = (($) ->
|
|
363
364
|
This event is [emitted]/(up.emit) when [AJAX requests](/up.ajax)
|
364
365
|
have [taken long to finish](/up:proxy:slow), but have finished now.
|
365
366
|
|
367
|
+
See [`up:proxy:slow`](/up:proxy:slow) for more documentation on
|
368
|
+
how to use this event for implementing a spinner that shows during
|
369
|
+
long-running requests.
|
370
|
+
|
366
371
|
@event up:proxy:recover
|
367
372
|
@stable
|
368
373
|
###
|
@@ -46,11 +46,11 @@ up.syntax = (($) ->
|
|
46
46
|
Registers a function to be called whenever an element with
|
47
47
|
the given selector is inserted into the DOM.
|
48
48
|
|
49
|
-
|
49
|
+
up.compiler('.action', function($element) {
|
50
50
|
// your code here
|
51
51
|
});
|
52
52
|
|
53
|
-
|
53
|
+
The functions will be called on elements maching `.action` when
|
54
54
|
the page loads, or whenever a matching fragment is [updated through Unpoly](/up.replace)
|
55
55
|
later.
|
56
56
|
|
@@ -135,7 +135,7 @@ up.syntax = (($) ->
|
|
135
135
|
{ lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
|
136
136
|
]"></div>
|
137
137
|
|
138
|
-
The JSON will parsed and handed to your
|
138
|
+
The JSON will parsed and handed to your compiler as a second argument:
|
139
139
|
|
140
140
|
up.compiler('.google-map', function($element, pins) {
|
141
141
|
|
@@ -195,7 +195,7 @@ up.syntax = (($) ->
|
|
195
195
|
@param {Function($element, data)} compiler
|
196
196
|
The function to call when a matching element is inserted.
|
197
197
|
The function takes the new element as the first argument (as a jQuery object).
|
198
|
-
If the element has an `up-data` attribute, its value is parsed as JSON
|
198
|
+
If the element has an [`up-data`](/up-data) attribute, its value is parsed as JSON
|
199
199
|
and passed as a second argument.
|
200
200
|
|
201
201
|
The function may return a destructor function that destroys the compiled
|
@@ -324,11 +324,21 @@ up.syntax = (($) ->
|
|
324
324
|
destroyer()
|
325
325
|
|
326
326
|
###*
|
327
|
-
Checks if the given element has an `up-data` attribute.
|
327
|
+
Checks if the given element has an [`up-data`](/up-data) attribute.
|
328
328
|
If yes, parses the attribute value as JSON and returns the parsed object.
|
329
329
|
|
330
330
|
Returns an empty object if the element has no `up-data` attribute.
|
331
331
|
|
332
|
+
\#\#\#\# Example
|
333
|
+
|
334
|
+
You have an element with JSON data serialized into an `up-data` attribute:
|
335
|
+
|
336
|
+
<span class="person" up-data="{ age: 18, name: 'Bob' }">Bob</span>
|
337
|
+
|
338
|
+
Calling `up.syntax.data` will deserialize the JSON string into a Javascript object:
|
339
|
+
|
340
|
+
up.syntax.data('.person') // returns { age: 18, name: 'Bob' }
|
341
|
+
|
332
342
|
@function up.syntax.data
|
333
343
|
@param {String|Element|jQuery} elementOrSelector
|
334
344
|
@return
|
@@ -338,15 +348,44 @@ up.syntax = (($) ->
|
|
338
348
|
@experimental
|
339
349
|
###
|
340
350
|
|
341
|
-
|
351
|
+
###*
|
342
352
|
If an element annotated with [`up-data`] is inserted into the DOM,
|
343
353
|
Up will parse the JSON and pass the resulting object to any matching
|
344
|
-
[`up.compiler`](/up.
|
354
|
+
[`up.compiler`](/up.compiler) handlers.
|
355
|
+
|
356
|
+
For instance, a container for a [Google Map](https://developers.google.com/maps/documentation/javascript/tutorial)
|
357
|
+
might attach the location and names of its marker pins:
|
358
|
+
|
359
|
+
<div class="google-map" up-data="[
|
360
|
+
{ lat: 48.36, lng: 10.99, title: 'Friedberg' },
|
361
|
+
{ lat: 48.75, lng: 11.45, title: 'Ingolstadt' }
|
362
|
+
]"></div>
|
363
|
+
|
364
|
+
The JSON will parsed and handed to your compiler as a second argument:
|
365
|
+
|
366
|
+
up.compiler('.google-map', function($element, pins) {
|
367
|
+
|
368
|
+
var map = new google.maps.Map($element);
|
369
|
+
|
370
|
+
pins.forEach(function(pin) {
|
371
|
+
var position = new google.maps.LatLng(pin.lat, pin.lng);
|
372
|
+
new google.maps.Marker({
|
373
|
+
position: position,
|
374
|
+
map: map,
|
375
|
+
title: pin.title
|
376
|
+
});
|
377
|
+
});
|
378
|
+
|
379
|
+
});
|
345
380
|
|
346
381
|
Similarly, when an event is triggered on an element annotated with
|
347
382
|
[`up-data`], the parsed object will be passed to any matching
|
348
383
|
[`up.on`](/up.on) handlers.
|
349
384
|
|
385
|
+
up.on('click', '.google-map', function(event, $element, pins) {
|
386
|
+
console.log("There are %d pins on the clicked map", pins.length);
|
387
|
+
});
|
388
|
+
|
350
389
|
@selector [up-data]
|
351
390
|
@param {JSON} up-data
|
352
391
|
A serialized JSON string
|
@@ -943,6 +943,7 @@ up.util = (($) ->
|
|
943
943
|
if opts.relative == true
|
944
944
|
coordinates = $element.position()
|
945
945
|
else
|
946
|
+
# A relative context element is given
|
946
947
|
$context = $(opts.relative)
|
947
948
|
elementCoords = $element.offset()
|
948
949
|
if $context.is(document)
|
@@ -968,9 +969,9 @@ up.util = (($) ->
|
|
968
969
|
box.height = $element.outerHeight()
|
969
970
|
|
970
971
|
if opts.full
|
971
|
-
viewport =
|
972
|
-
box.right = viewport.width - (box.left + box.width)
|
973
|
-
box.bottom = viewport.height - (box.top + box.height)
|
972
|
+
$viewport = up.layout.viewportOf($element)
|
973
|
+
box.right = $viewport.width() - (box.left + box.width)
|
974
|
+
box.bottom = $viewport.height() - (box.top + box.height)
|
974
975
|
box
|
975
976
|
|
976
977
|
###*
|
data/lib/unpoly/rails/version.rb
CHANGED
@@ -49,15 +49,16 @@ describe 'up.link', ->
|
|
49
49
|
# so we don't lose the Jasmine runner interface.
|
50
50
|
up.history.config.popTargets = ['.container']
|
51
51
|
|
52
|
-
respondWith = (html) =>
|
52
|
+
respondWith = (html, title) =>
|
53
53
|
@lastRequest().respondWith
|
54
54
|
status: 200
|
55
55
|
contentType: 'text/html'
|
56
56
|
responseText: "<div class='container'><div class='target'>#{html}</div></div>"
|
57
|
+
responseHeaders: { 'X-Up-Title': title }
|
57
58
|
|
58
|
-
followAndRespond = ($link, html) ->
|
59
|
+
followAndRespond = ($link, html, title) ->
|
59
60
|
promise = up.follow($link)
|
60
|
-
respondWith(html)
|
61
|
+
respondWith(html, title)
|
61
62
|
promise
|
62
63
|
|
63
64
|
$link1 = affix('a[href="/one"][up-target=".target"]')
|
@@ -66,35 +67,41 @@ describe 'up.link', ->
|
|
66
67
|
$container = affix('.container')
|
67
68
|
$target = affix('.target').appendTo($container).text('original text')
|
68
69
|
|
69
|
-
followAndRespond($link1, 'text from one').then =>
|
70
|
+
followAndRespond($link1, 'text from one', 'title from one').then =>
|
70
71
|
expect($('.target')).toHaveText('text from one')
|
71
72
|
expect(location.pathname).toEqual('/one')
|
73
|
+
expect(document.title).toEqual('title from one')
|
72
74
|
|
73
|
-
followAndRespond($link2, 'text from two').then =>
|
75
|
+
followAndRespond($link2, 'text from two', 'title from two').then =>
|
74
76
|
expect($('.target')).toHaveText('text from two')
|
75
77
|
expect(location.pathname).toEqual('/two')
|
78
|
+
expect(document.title).toEqual('title from two')
|
76
79
|
|
77
|
-
followAndRespond($link3, 'text from three').then =>
|
80
|
+
followAndRespond($link3, 'text from three', 'title from three').then =>
|
78
81
|
expect($('.target')).toHaveText('text from three')
|
79
82
|
expect(location.pathname).toEqual('/three')
|
83
|
+
expect(document.title).toEqual('title from three')
|
80
84
|
|
81
85
|
history.back()
|
82
86
|
@setTimer 50, =>
|
83
|
-
respondWith('restored text from two')
|
87
|
+
respondWith('restored text from two', 'restored title from two')
|
84
88
|
expect($('.target')).toHaveText('restored text from two')
|
85
89
|
expect(location.pathname).toEqual('/two')
|
90
|
+
expect(document.title).toEqual('restored title from two')
|
86
91
|
|
87
92
|
history.back()
|
88
93
|
@setTimer 50, =>
|
89
|
-
respondWith('restored text from one')
|
94
|
+
respondWith('restored text from one', 'restored title from one')
|
90
95
|
expect($('.target')).toHaveText('restored text from one')
|
91
96
|
expect(location.pathname).toEqual('/one')
|
97
|
+
expect(document.title).toEqual('restored title from one')
|
92
98
|
|
93
99
|
history.forward()
|
94
100
|
@setTimer 50, =>
|
95
101
|
# Since the response is cached, we don't have to respond
|
96
|
-
expect($('.target')).toHaveText('restored text from two')
|
102
|
+
expect($('.target')).toHaveText('restored text from two', 'restored title from two')
|
97
103
|
expect(location.pathname).toEqual('/two')
|
104
|
+
expect(document.title).toEqual('restored title from two')
|
98
105
|
|
99
106
|
done()
|
100
107
|
|
@@ -190,6 +197,27 @@ describe 'up.link', ->
|
|
190
197
|
up.follow($link)
|
191
198
|
expect(up.browser.loadPage).toHaveBeenCalledWith('/path', { method: 'PUT' })
|
192
199
|
|
200
|
+
describe 'up.link.makeFollowable', ->
|
201
|
+
|
202
|
+
it "adds [up-follow] to a link that wouldn't otherwise be handled by Unpoly", ->
|
203
|
+
$link = affix('a[href="/path"]').text('label')
|
204
|
+
up.link.makeFollowable($link)
|
205
|
+
expect($link.attr('up-follow')).toEqual('')
|
206
|
+
|
207
|
+
it "does not add [up-follow] to a link that is already [up-target]", ->
|
208
|
+
$link = affix('a[href="/path"][up-target=".target"]').text('label')
|
209
|
+
up.link.makeFollowable($link)
|
210
|
+
expect($link.attr('up-follow')).toBeMissing()
|
211
|
+
|
212
|
+
it "does not add [up-follow] to a link that is already [up-modal]", ->
|
213
|
+
$link = affix('a[href="/path"][up-modal=".target"]').text('label')
|
214
|
+
up.link.makeFollowable($link)
|
215
|
+
expect($link.attr('up-follow')).toBeMissing()
|
216
|
+
|
217
|
+
it "does not add [up-follow] to a link that is already [up-popup]", ->
|
218
|
+
$link = affix('a[href="/path"][up-popup=".target"]').text('label')
|
219
|
+
up.link.makeFollowable($link)
|
220
|
+
expect($link.attr('up-follow')).toBeMissing()
|
193
221
|
|
194
222
|
describe 'up.visit', ->
|
195
223
|
|
@@ -301,6 +329,48 @@ describe 'up.link', ->
|
|
301
329
|
Trigger.mousedown(@$link, metaKey: true)
|
302
330
|
expect(@followSpy).not.toHaveBeenCalled()
|
303
331
|
|
332
|
+
describe '[up-dash]', ->
|
333
|
+
|
334
|
+
it "is a shortcut for [up-preload], [up-instant] and [up-target], using [up-dash]'s value as [up-target]", ->
|
335
|
+
$link = affix('a[href="/path"][up-dash=".target"]').text('label')
|
336
|
+
up.hello($link)
|
337
|
+
expect($link.attr('up-preload')).toEqual('')
|
338
|
+
expect($link.attr('up-instant')).toEqual('')
|
339
|
+
expect($link.attr('up-target')).toEqual('.target')
|
340
|
+
|
341
|
+
it "adds [up-follow] attribute if [up-dash]'s value is 'true'", ->
|
342
|
+
$link = affix('a[href="/path"][up-dash="true"]').text('label')
|
343
|
+
up.hello($link)
|
344
|
+
expect($link.attr('up-follow')).toEqual('')
|
345
|
+
|
346
|
+
it "adds [up-follow] attribute if [up-dash] is present, but has no value", ->
|
347
|
+
$link = affix('a[href="/path"][up-dash]').text('label')
|
348
|
+
up.hello($link)
|
349
|
+
expect($link.attr('up-follow')).toEqual('')
|
350
|
+
|
351
|
+
it "does not add an [up-follow] attribute if [up-dash] is 'true', but [up-target] is present", ->
|
352
|
+
$link = affix('a[href="/path"][up-dash="true"][up-target=".target"]').text('label')
|
353
|
+
up.hello($link)
|
354
|
+
expect($link.attr('up-follow')).toBeMissing()
|
355
|
+
expect($link.attr('up-target')).toEqual('.target')
|
356
|
+
|
357
|
+
it "does not add an [up-follow] attribute if [up-dash] is 'true', but [up-modal] is present", ->
|
358
|
+
$link = affix('a[href="/path"][up-dash="true"][up-modal=".target"]').text('label')
|
359
|
+
up.hello($link)
|
360
|
+
expect($link.attr('up-follow')).toBeMissing()
|
361
|
+
expect($link.attr('up-modal')).toEqual('.target')
|
362
|
+
|
363
|
+
it "does not add an [up-follow] attribute if [up-dash] is 'true', but [up-popup] is present", ->
|
364
|
+
$link = affix('a[href="/path"][up-dash="true"][up-popup=".target"]').text('label')
|
365
|
+
up.hello($link)
|
366
|
+
expect($link.attr('up-follow')).toBeMissing()
|
367
|
+
expect($link.attr('up-popup')).toEqual('.target')
|
368
|
+
|
369
|
+
it "removes the [up-dash] attribute when it's done", ->
|
370
|
+
$link = affix('a[href="/path"]').text('label')
|
371
|
+
up.hello($link)
|
372
|
+
expect($link.attr('up-dash')).toBeMissing()
|
373
|
+
|
304
374
|
describe '[up-expand]', ->
|
305
375
|
|
306
376
|
it 'copies up-related attributes of a contained link', ->
|
@@ -332,6 +402,31 @@ describe 'up.link', ->
|
|
332
402
|
up.hello($area)
|
333
403
|
expect($area.attr('up-follow')).toEqual('')
|
334
404
|
|
405
|
+
it 'can be used to enlarge the click area of a link', ->
|
406
|
+
$area = affix('div[up-expand] a[href="/path"]')
|
407
|
+
up.hello($area)
|
408
|
+
spyOn(up, 'replace')
|
409
|
+
$area.get(0).click()
|
410
|
+
expect(up.replace).toHaveBeenCalled()
|
411
|
+
|
412
|
+
it 'does not trigger multiple replaces when the user clicks on the expanded area of an up-instant link (bugfix)', ->
|
413
|
+
$area = affix('div[up-expand] a[href="/path"][up-instant]')
|
414
|
+
up.hello($area)
|
415
|
+
spyOn(up, 'replace')
|
416
|
+
Trigger.mousedown($area)
|
417
|
+
Trigger.click($area)
|
418
|
+
expect(up.replace.calls.count()).toEqual(1)
|
419
|
+
|
420
|
+
it 'does not add an up-follow attribute if the expanded link is [up-dash] with a selector (bugfix)', ->
|
421
|
+
$area = affix('div[up-expand] a[href="/path"][up-dash=".element"]')
|
422
|
+
up.hello($area)
|
423
|
+
expect($area.attr('up-follow')).toBeMissing()
|
424
|
+
|
425
|
+
it 'does not an up-follow attribute if the expanded link is [up-dash] without a selector (bugfix)', ->
|
426
|
+
$area = affix('div[up-expand] a[href="/path"][up-dash]')
|
427
|
+
up.hello($area)
|
428
|
+
expect($area.attr('up-follow')).toEqual('')
|
429
|
+
|
335
430
|
describe 'with a CSS selector in the property value', ->
|
336
431
|
|
337
432
|
it "expands the contained link that matches the selector", ->
|
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.22.
|
4
|
+
version: 0.22.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Henning Koch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|