turbolinks 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -9
- data/lib/assets/javascripts/turbolinks.js.coffee +38 -24
- data/lib/turbolinks.rb +14 -11
- data/test/config.ru +7 -1
- data/test/index.html +6 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81b3f537e66ca790fc7d3582de6eb2eab6d1b9b9
|
4
|
+
data.tar.gz: b9f0c3ed62c42062e678b92676012d08ad8decfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34ec20469fdcfa96705c4b3b1639c963196568cf78c46f9d24083849d93c4193acb5cca47b28e2caf7edcc2907586e1793394aa04a80304afe607fd3d8f91429
|
7
|
+
data.tar.gz: faf3469bb52dc2adba9e96634946c5b5cc775c5be9a77ead70cde60a80a29b3737f3634b68d7e1d31a5bb6ab1bed3891bbd40219d99cdc26b89387fc08757892
|
data/README.md
CHANGED
@@ -27,20 +27,26 @@ Turbolinks is designed to be as light-weight as possible (so you won't think twi
|
|
27
27
|
Events
|
28
28
|
------
|
29
29
|
|
30
|
-
|
30
|
+
With Turbolinks pages will change without a full reload, so you can't rely on `DOMContentLoaded` or `jQuery.ready()` to trigger your code. Instead Turbolinks fires events on `document` to provide hooks into the lifecycle of the page:
|
31
31
|
|
32
|
-
*
|
33
|
-
* `page:
|
34
|
-
* `page:
|
35
|
-
* `page:change`
|
36
|
-
* `page:
|
32
|
+
*Load* a fresh version of a page from the server:
|
33
|
+
* `page:fetch` starting to fetch a new target page
|
34
|
+
* `page:receive` the page has been fetched from the server, but not yet parsed
|
35
|
+
* `page:change` the page has been parsed and changed to the new version
|
36
|
+
* `page:load` is fired at the end of the loading process.
|
37
|
+
|
38
|
+
Turbolinks caches 10 of these page loads. It listens to the [popstate](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history#The_popstate_event) event and attempts restore page state from the cache when it's triggered. When `popstate` is fired the following process happens:
|
39
|
+
|
40
|
+
*Restore* a cached page from the client-side cache:
|
41
|
+
* `page:change` page has changed to the cached page.
|
42
|
+
* `page:restore` is fired at the end of restore process.
|
37
43
|
|
38
44
|
So if you wanted to have a client-side spinner, you could listen for `page:fetch` to start it and `page:receive` to stop it.
|
39
45
|
|
40
46
|
document.addEventListener("page:fetch", startSpinner);
|
41
47
|
document.addEventListener("page:receive", stopSpinner);
|
42
48
|
|
43
|
-
|
49
|
+
DOM transformations that are idempotent are best. If you have transformations that are not, hook them to happen only on `page:load` instead of `page:change` (as that would run them again on the cached pages).
|
44
50
|
|
45
51
|
Initialization
|
46
52
|
--------------
|
@@ -62,6 +68,13 @@ Opting out of Turbolinks
|
|
62
68
|
|
63
69
|
By default, all internal HTML links will be funneled through Turbolinks, but you can opt out by marking links or their parent container with `data-no-turbolink`. For example, if you mark a div with `data-no-turbolink`, then all links inside of that div will be treated as regular links. If you mark the body, every link on that entire page will be treated as regular links.
|
64
70
|
|
71
|
+
```html
|
72
|
+
<a href="/">Home (via Turbolinks)</a>
|
73
|
+
<div id="some-div" data-no-turbolink>
|
74
|
+
<a href="/">Home (without Turbolinks)</a>
|
75
|
+
</div>
|
76
|
+
```
|
77
|
+
|
65
78
|
Note that internal links to files not ending in .html, or having no extension, will automatically be opted out of Turbolinks. So links to /images/panda.gif will just work as expected.
|
66
79
|
|
67
80
|
Also, Turbolinks is installed as the last click handler for links. So if you install another handler that calls event.preventDefault(), Turbolinks will not run. This ensures that you can safely use Turbolinks with stuff like `data-method`, `data-remote`, or `data-confirm` from Rails.
|
@@ -97,7 +110,13 @@ Evaluating script tags
|
|
97
110
|
|
98
111
|
Turbolinks will evaluate any script tags in pages it visit, if those tags do not have a type or if the type is text/javascript. All other script tags will be ignored.
|
99
112
|
|
100
|
-
As a rule of thumb when switching to Turbolinks, move all of your javascript tags inside the `head` and then work backwards, only moving javascript code back to the body if absolutely necessary.
|
113
|
+
As a rule of thumb when switching to Turbolinks, move all of your javascript tags inside the `head` and then work backwards, only moving javascript code back to the body if absolutely necessary. If you have any script tags in the body you do not want to be re-evaluated then you can set the `data-turbolinks-eval` attribute to `false`:
|
114
|
+
|
115
|
+
```html
|
116
|
+
<script type="text/javascript" data-turbolinks-eval=false>
|
117
|
+
console.log("I'm only run once on the initial page load");
|
118
|
+
</script>
|
119
|
+
```
|
101
120
|
|
102
121
|
Triggering a Turbolinks visit manually
|
103
122
|
---------------------------------------
|
@@ -116,7 +135,7 @@ Compatibility
|
|
116
135
|
|
117
136
|
Turbolinks is designed to work with any browser that fully supports pushState and all the related APIs. This includes Safari 6.0+ (but not Safari 5.1.x!), IE10, and latest Chromes and Firefoxes.
|
118
137
|
|
119
|
-
Do note that existing JavaScript libraries may not all be compatible with Turbolinks out of the box due to the change in instantiation cycle. You might very well have to modify them to work with Turbolinks' new set of events. For help with this, check out the [Turbolinks Compatibility](http://reed.github.
|
138
|
+
Do note that existing JavaScript libraries may not all be compatible with Turbolinks out of the box due to the change in instantiation cycle. You might very well have to modify them to work with Turbolinks' new set of events. For help with this, check out the [Turbolinks Compatibility](http://reed.github.io/turbolinks-compatibility) project.
|
120
139
|
|
121
140
|
|
122
141
|
Installation
|
@@ -28,14 +28,12 @@ fetchReplacement = (url) ->
|
|
28
28
|
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
29
29
|
xhr.setRequestHeader 'X-XHR-Referer', referer
|
30
30
|
|
31
|
-
xhr.onload =
|
31
|
+
xhr.onload = ->
|
32
32
|
triggerEvent 'page:receive'
|
33
|
-
|
34
|
-
if
|
35
|
-
document.location.reload()
|
36
|
-
else
|
33
|
+
|
34
|
+
if doc = validateResponse()
|
37
35
|
changePage extractTitleAndBody(doc)...
|
38
|
-
reflectRedirectedUrl
|
36
|
+
reflectRedirectedUrl()
|
39
37
|
if document.location.hash
|
40
38
|
document.location.href = document.location.href
|
41
39
|
else
|
@@ -87,7 +85,7 @@ changePage = (title, body, csrfToken, runScripts) ->
|
|
87
85
|
triggerEvent 'page:change'
|
88
86
|
|
89
87
|
executeScriptTags = ->
|
90
|
-
scripts = Array::slice.call document.body.
|
88
|
+
scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])'
|
91
89
|
for script in scripts when script.type in ['', 'text/javascript']
|
92
90
|
copy = document.createElement 'script'
|
93
91
|
copy.setAttribute attr.name, attr.value for attr in script.attributes
|
@@ -107,9 +105,10 @@ reflectNewUrl = (url) ->
|
|
107
105
|
referer = document.location.href
|
108
106
|
window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', url
|
109
107
|
|
110
|
-
reflectRedirectedUrl =
|
111
|
-
if
|
112
|
-
|
108
|
+
reflectRedirectedUrl = ->
|
109
|
+
if location = xhr.getResponseHeader 'X-XHR-Redirected-To'
|
110
|
+
preservedHash = if removeHash(location) is location then document.location.hash else ''
|
111
|
+
window.history.replaceState currentState, '', location + preservedHash
|
113
112
|
|
114
113
|
rememberCurrentUrl = ->
|
115
114
|
window.history.replaceState { turbolinks: true, position: Date.now() }, '', document.location.href
|
@@ -137,27 +136,42 @@ removeHash = (url) ->
|
|
137
136
|
link.href = url
|
138
137
|
link.href.replace link.hash, ''
|
139
138
|
|
140
|
-
|
141
139
|
triggerEvent = (name) ->
|
142
140
|
event = document.createEvent 'Events'
|
143
141
|
event.initEvent name, true, true
|
144
142
|
document.dispatchEvent event
|
145
143
|
|
146
144
|
|
147
|
-
|
148
|
-
|
145
|
+
validateResponse = ->
|
146
|
+
clientOrServerError = ->
|
147
|
+
400 <= xhr.status < 600
|
148
|
+
|
149
|
+
invalidContent = ->
|
150
|
+
!xhr.getResponseHeader('Content-Type').match /^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/
|
151
|
+
|
152
|
+
extractTrackAssets = (doc) ->
|
153
|
+
(node.src || node.href) for node in doc.head.childNodes when node.getAttribute?('data-turbolinks-track')?
|
149
154
|
|
150
|
-
|
151
|
-
|
155
|
+
assetsChanged = (doc) ->
|
156
|
+
loadedAssets ||= extractTrackAssets document
|
157
|
+
fetchedAssets = extractTrackAssets doc
|
158
|
+
fetchedAssets.length isnt loadedAssets.length or intersection(fetchedAssets, loadedAssets).length isnt loadedAssets.length
|
152
159
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
fetchedAssets.length isnt loadedAssets.length or intersection(fetchedAssets, loadedAssets).length isnt loadedAssets.length
|
160
|
+
intersection = (a, b) ->
|
161
|
+
[a, b] = [b, a] if a.length > b.length
|
162
|
+
value for value in a when value in b
|
157
163
|
|
158
|
-
|
159
|
-
|
160
|
-
|
164
|
+
if clientOrServerError()
|
165
|
+
# Workaround for WebKit bug (https://bugs.webkit.org/show_bug.cgi?id=93506)
|
166
|
+
url = document.location.href
|
167
|
+
window.history.replaceState null, '', '#'
|
168
|
+
window.location.replace url
|
169
|
+
false
|
170
|
+
else if invalidContent() or assetsChanged (doc = createDocument xhr.responseText)
|
171
|
+
window.location.reload()
|
172
|
+
false
|
173
|
+
else
|
174
|
+
doc
|
161
175
|
|
162
176
|
extractTitleAndBody = (doc) ->
|
163
177
|
title = doc.querySelector 'title'
|
@@ -167,12 +181,12 @@ CSRFToken =
|
|
167
181
|
get: (doc = document) ->
|
168
182
|
node: tag = doc.querySelector 'meta[name="csrf-token"]'
|
169
183
|
token: tag?.getAttribute? 'content'
|
170
|
-
|
184
|
+
|
171
185
|
update: (latest) ->
|
172
186
|
current = @get()
|
173
187
|
if current.token? and latest? and current.token isnt latest
|
174
188
|
current.node.setAttribute 'content', latest
|
175
|
-
|
189
|
+
|
176
190
|
browserCompatibleDocumentParser = ->
|
177
191
|
createDocumentUsingParser = (html) ->
|
178
192
|
(new DOMParser).parseFromString html, 'text/html'
|
data/lib/turbolinks.rb
CHANGED
@@ -8,15 +8,18 @@ module Turbolinks
|
|
8
8
|
|
9
9
|
private
|
10
10
|
def _compute_redirect_to_location_with_xhr_referer(options)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
session[:_turbolinks_redirect_to] =
|
12
|
+
if options == :back && request.headers["X-XHR-Referer"]
|
13
|
+
_compute_redirect_to_location_without_xhr_referer(request.headers["X-XHR-Referer"])
|
14
|
+
else
|
15
|
+
_compute_redirect_to_location_without_xhr_referer(options)
|
16
|
+
end
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
-
|
19
|
+
def set_xhr_redirected_to
|
20
|
+
if session[:_turbolinks_redirect_to]
|
21
|
+
response.headers['X-XHR-Redirected-To'] = session.delete :_turbolinks_redirect_to
|
22
|
+
end
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
@@ -30,8 +33,8 @@ module Turbolinks
|
|
30
33
|
module XDomainBlocker
|
31
34
|
private
|
32
35
|
def same_origin?(a, b)
|
33
|
-
a = URI.parse(a)
|
34
|
-
b = URI.parse(b)
|
36
|
+
a = URI.parse URI.escape(a)
|
37
|
+
b = URI.parse URI.escape(b)
|
35
38
|
[a.scheme, a.host, a.port] == [b.scheme, b.host, b.port]
|
36
39
|
end
|
37
40
|
|
@@ -48,10 +51,10 @@ module Turbolinks
|
|
48
51
|
initializer :turbolinks_xhr_headers do |config|
|
49
52
|
ActionController::Base.class_eval do
|
50
53
|
include XHRHeaders, Cookies, XDomainBlocker
|
51
|
-
before_filter :
|
54
|
+
before_filter :set_xhr_redirected_to, :set_request_method_cookie
|
52
55
|
after_filter :abort_xdomain_redirect
|
53
56
|
end
|
54
|
-
|
57
|
+
|
55
58
|
ActionDispatch::Request.class_eval do
|
56
59
|
def referer
|
57
60
|
self.headers['X-XHR-Referer'] || super
|
data/test/config.ru
CHANGED
data/test/index.html
CHANGED
@@ -21,12 +21,17 @@
|
|
21
21
|
<li><a href="#">Hash link</a></li>
|
22
22
|
<li><a href="/reload.html#foo">New assets track with hash link</a></li>
|
23
23
|
<li><h5>If you stop the server or go into airplane/offline mode</h5></li>
|
24
|
-
<li><a href="/doesnotexist.html">A page
|
24
|
+
<li><a href="/doesnotexist.html">A page with client error (4xx, rfc2616 sec. 10.4) should error out</a></li>
|
25
|
+
<li><a href="/500">Also server errors (5xx, rfc2616 sec. 10.5) should error out</a></li>
|
25
26
|
<li><a href="/fallback.html">A page that has a fallback in appcache should fallback</a></li>
|
26
27
|
</ul>
|
27
28
|
|
28
29
|
<div style="background:#ccc;height:5000px;width:200px;">
|
29
30
|
</div>
|
30
31
|
<iframe height='1' scrolling='no' src='/offline.html' style='display: none;' width='1'></iframe>
|
32
|
+
|
33
|
+
<script type="text/javascript" data-turbolinks-eval=false>
|
34
|
+
console.log("turbolinks-eval-false script fired. This should only happen on the initial page load.");
|
35
|
+
</script>
|
31
36
|
</body>
|
32
37
|
</html>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbolinks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: coffee-rails
|