turbolinks 0.6.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +15 -0
- data/lib/assets/javascripts/turbolinks.js.coffee +44 -20
- data/lib/turbolinks.rb +9 -2
- data/test/index.html +4 -0
- data/test/manifest.appcache +10 -0
- data/test/offline.html +19 -0
- metadata +11 -9
data/README.md
CHANGED
@@ -37,6 +37,21 @@ Since pages will change without a full reload with Turbolinks, you can't by defa
|
|
37
37
|
So if you wanted to have a client-side spinner, you could listen for `page:fetch` to start it and `page:change` to stop it. If you have DOM transformation that are not idempotent (the best way), you can hook them to happen only on `page:load` instead of `page:change` (as that would run them again on the cached pages).
|
38
38
|
|
39
39
|
|
40
|
+
Initialization
|
41
|
+
--------------
|
42
|
+
|
43
|
+
Turbolinks will be enabled **only** if the server has rendered a `GET` request.
|
44
|
+
|
45
|
+
Some examples, given a standard RESTful resource:
|
46
|
+
|
47
|
+
* `POST :create` => resource successfully created => redirect to `GET :show`
|
48
|
+
* Turbolinks **ENABLED**
|
49
|
+
* `POST :create` => resource creation failed => render `:new`
|
50
|
+
* Turbolinks **DISABLED**
|
51
|
+
|
52
|
+
**Why not all request types?** Some browsers track the request method of each page load, but triggering pushState methods don't change this value. This could lead to the situation where pressing the browser's reload button on a page that was fetched with Turbolinks would attempt a `POST` (or something other than `GET`) because the last full page load used that method.
|
53
|
+
|
54
|
+
|
40
55
|
Opting out of Turbolinks
|
41
56
|
------------------------
|
42
57
|
|
@@ -4,6 +4,8 @@ referer = document.location.href
|
|
4
4
|
loadedAssets = null
|
5
5
|
pageCache = {}
|
6
6
|
createDocument = null
|
7
|
+
requestMethod = document.cookie.match(/request_method=(\w+)/)?[1].toUpperCase() or ''
|
8
|
+
xhr = null
|
7
9
|
|
8
10
|
visit = (url) ->
|
9
11
|
if browserSupportsPushState
|
@@ -17,11 +19,16 @@ visit = (url) ->
|
|
17
19
|
fetchReplacement = (url) ->
|
18
20
|
triggerEvent 'page:fetch'
|
19
21
|
|
22
|
+
# Remove hash from url to ensure IE 10 compatibility
|
23
|
+
safeUrl = removeHash url
|
24
|
+
|
25
|
+
xhr.abort() if xhr
|
20
26
|
xhr = new XMLHttpRequest
|
21
|
-
xhr.open 'GET',
|
27
|
+
xhr.open 'GET', safeUrl, true
|
22
28
|
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
23
29
|
xhr.setRequestHeader 'X-XHR-Referer', referer
|
24
|
-
|
30
|
+
xhr.onabort = ->
|
31
|
+
xhr = null
|
25
32
|
xhr.onload = =>
|
26
33
|
doc = createDocument xhr.responseText
|
27
34
|
|
@@ -30,10 +37,16 @@ fetchReplacement = (url) ->
|
|
30
37
|
else
|
31
38
|
changePage extractTitleAndBody(doc)...
|
32
39
|
reflectRedirectedUrl xhr
|
33
|
-
|
40
|
+
if document.location.hash
|
41
|
+
document.location.href = document.location.href
|
42
|
+
else
|
43
|
+
resetScrollPosition()
|
34
44
|
triggerEvent 'page:load'
|
45
|
+
xhr = null
|
35
46
|
|
36
|
-
xhr.
|
47
|
+
xhr.onerror = ->
|
48
|
+
document.location.href = url
|
49
|
+
xhr = null
|
37
50
|
|
38
51
|
xhr.send()
|
39
52
|
|
@@ -73,15 +86,12 @@ changePage = (title, body, runScripts) ->
|
|
73
86
|
|
74
87
|
executeScriptTags = ->
|
75
88
|
for script in document.body.getElementsByTagName 'script' when script.type in ['', 'text/javascript']
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
parent.insertBefore copy, parent.childNodes[0]
|
83
|
-
else
|
84
|
-
window.eval(script.innerHTML)
|
89
|
+
copy = document.createElement 'script'
|
90
|
+
copy.setAttribute attr.name, attr.value for attr in script.attributes
|
91
|
+
copy.appendChild document.createTextNode script.innerHTML
|
92
|
+
{ parentNode, nextSibling } = script
|
93
|
+
parentNode.removeChild script
|
94
|
+
parentNode.insertBefore copy, nextSibling
|
85
95
|
|
86
96
|
|
87
97
|
reflectNewUrl = (url) ->
|
@@ -90,7 +100,7 @@ reflectNewUrl = (url) ->
|
|
90
100
|
window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', url
|
91
101
|
|
92
102
|
reflectRedirectedUrl = (xhr) ->
|
93
|
-
|
103
|
+
if (location = xhr.getResponseHeader 'X-XHR-Current-Location') and location isnt document.location.pathname + document.location.search
|
94
104
|
window.history.replaceState currentState, '', location + document.location.hash
|
95
105
|
|
96
106
|
rememberCurrentUrl = ->
|
@@ -112,6 +122,13 @@ recallScrollPosition = (page) ->
|
|
112
122
|
resetScrollPosition = ->
|
113
123
|
window.scrollTo 0, 0
|
114
124
|
|
125
|
+
removeHash = (url) ->
|
126
|
+
link = url
|
127
|
+
unless url.href?
|
128
|
+
link = document.createElement 'A'
|
129
|
+
link.href = url
|
130
|
+
link.href.replace link.hash, ''
|
131
|
+
|
115
132
|
|
116
133
|
triggerEvent = (name) ->
|
117
134
|
event = document.createEvent 'Events'
|
@@ -163,7 +180,7 @@ installClickHandlerLast = (event) ->
|
|
163
180
|
handleClick = (event) ->
|
164
181
|
unless event.defaultPrevented
|
165
182
|
link = extractLink event
|
166
|
-
if link
|
183
|
+
if link.nodeName is 'A' and !ignoreClick(event, link)
|
167
184
|
visit link.href
|
168
185
|
event.preventDefault()
|
169
186
|
|
@@ -177,7 +194,7 @@ crossOriginLink = (link) ->
|
|
177
194
|
location.protocol isnt link.protocol or location.host isnt link.host
|
178
195
|
|
179
196
|
anchoredLink = (link) ->
|
180
|
-
((link.hash and
|
197
|
+
((link.hash and removeHash(link)) is removeHash(location)) or
|
181
198
|
(link.href is location.href + '#')
|
182
199
|
|
183
200
|
nonHtmlLink = (link) ->
|
@@ -199,14 +216,21 @@ ignoreClick = (event, link) ->
|
|
199
216
|
crossOriginLink(link) or anchoredLink(link) or nonHtmlLink(link) or noTurbolink(link) or targetLink(link) or nonStandardClick(event)
|
200
217
|
|
201
218
|
|
219
|
+
initializeTurbolinks = ->
|
220
|
+
document.addEventListener 'click', installClickHandlerLast, true
|
221
|
+
window.addEventListener 'popstate', (event) ->
|
222
|
+
fetchHistory event.state if event.state?.turbolinks
|
223
|
+
|
202
224
|
browserSupportsPushState =
|
203
225
|
window.history and window.history.pushState and window.history.replaceState and window.history.state != undefined
|
204
226
|
|
205
|
-
|
206
|
-
|
227
|
+
browserIsntBuggy =
|
228
|
+
!navigator.userAgent.match /CriOS\//
|
207
229
|
|
208
|
-
|
209
|
-
|
230
|
+
requestMethodIsSafe =
|
231
|
+
requestMethod in ['GET','']
|
232
|
+
|
233
|
+
initializeTurbolinks() if browserSupportsPushState and browserIsntBuggy and requestMethodIsSafe
|
210
234
|
|
211
235
|
# Call Turbolinks.visit(url) from client code
|
212
236
|
@Turbolinks = { visit }
|
data/lib/turbolinks.rb
CHANGED
@@ -20,11 +20,18 @@ module Turbolinks
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
module Cookies
|
24
|
+
private
|
25
|
+
def set_request_method_cookie
|
26
|
+
cookies[:request_method] = request.request_method
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
23
30
|
class Engine < ::Rails::Engine
|
24
31
|
initializer :turbolinks_xhr_headers do |config|
|
25
32
|
ActionController::Base.class_eval do
|
26
|
-
include XHRHeaders
|
27
|
-
before_filter :set_xhr_current_location
|
33
|
+
include XHRHeaders, Cookies
|
34
|
+
before_filter :set_xhr_current_location, :set_request_method_cookie
|
28
35
|
end
|
29
36
|
end
|
30
37
|
end
|
data/test/index.html
CHANGED
@@ -20,9 +20,13 @@
|
|
20
20
|
<li><a href="/dummy.gif?12345">Query Param Image Link</a></li>
|
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
|
+
<li><h5>If you stop the server or go into airplane/offline mode</h5></li>
|
24
|
+
<li><a href="/doesnotexist.html">A page that errors should error out</a></li>
|
25
|
+
<li><a href="/fallback.html">A page that has a fallback in appcache should fallback</a></li>
|
23
26
|
</ul>
|
24
27
|
|
25
28
|
<div style="background:#ccc;height:5000px;width:200px;">
|
26
29
|
</div>
|
30
|
+
<iframe height='1' scrolling='no' src='/offline.html' style='display: none;' width='1'></iframe>
|
27
31
|
</body>
|
28
32
|
</html>
|
data/test/offline.html
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en" manifest="/manifest.appcache">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<title>Home</title>
|
6
|
+
<script type="text/javascript" src="/js/turbolinks.js"></script>
|
7
|
+
<script type="text/javascript">
|
8
|
+
document.addEventListener("page:change", function() {
|
9
|
+
console.log("page changed");
|
10
|
+
});
|
11
|
+
</script>
|
12
|
+
</head>
|
13
|
+
<body class="page-offline">
|
14
|
+
<h1>Offline Mode Fallback</h1>
|
15
|
+
<ul>
|
16
|
+
<li><a href="/index.html">Home</a></li>
|
17
|
+
</ul>
|
18
|
+
</body>
|
19
|
+
</html>
|
metadata
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbolinks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
4
5
|
prerelease:
|
5
|
-
version: 0.6.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- David Heinemeier Hansson
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
|
15
|
+
name: coffee-rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
19
|
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: '0'
|
20
|
-
none: false
|
21
|
-
name: coffee-rails
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
25
26
|
requirements:
|
26
27
|
- - ! '>='
|
27
28
|
- !ruby/object:Gem::Version
|
28
29
|
version: '0'
|
29
|
-
none: false
|
30
30
|
description:
|
31
31
|
email: david@loudthinking.com
|
32
32
|
executables: []
|
@@ -40,6 +40,8 @@ files:
|
|
40
40
|
- test/config.ru
|
41
41
|
- test/dummy.gif
|
42
42
|
- test/index.html
|
43
|
+
- test/manifest.appcache
|
44
|
+
- test/offline.html
|
43
45
|
- test/other.html
|
44
46
|
- test/reload.html
|
45
47
|
homepage:
|
@@ -49,17 +51,17 @@ rdoc_options: []
|
|
49
51
|
require_paths:
|
50
52
|
- lib
|
51
53
|
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
52
55
|
requirements:
|
53
56
|
- - ! '>='
|
54
57
|
- !ruby/object:Gem::Version
|
55
58
|
version: '0'
|
56
|
-
none: false
|
57
59
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
58
61
|
requirements:
|
59
62
|
- - ! '>='
|
60
63
|
- !ruby/object:Gem::Version
|
61
64
|
version: '0'
|
62
|
-
none: false
|
63
65
|
requirements: []
|
64
66
|
rubyforge_project:
|
65
67
|
rubygems_version: 1.8.23
|