turbolinks 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +13 -5
- data/lib/assets/javascripts/turbolinks.js.coffee +87 -38
- data/test/config.ru +1 -1
- data/test/dummy.gif +0 -0
- data/test/index.html +9 -4
- data/test/other.html +2 -2
- metadata +3 -2
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Turbolinks
|
2
2
|
===========
|
3
3
|
|
4
|
-
Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change,
|
4
|
+
Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, we keep the current page instance alive and replace only the body and the title in the head (and potentially spend extra HTTP requests checking if the assets are up-to-date). Think CGI vs persistent process.
|
5
5
|
|
6
6
|
This is similar to pjax, but instead of worrying about what element on the page to replace, and tailoring the server-side response to fit, we replace the entire body. This means that you get the bulk of the speed benefits from pjax (no recompiling of the JavaScript or CSS) without having to tailor the server-side response. It just works.
|
7
7
|
|
@@ -14,10 +14,10 @@ No jQuery or any other framework
|
|
14
14
|
Turbolinks is designed to be as light-weight as possible (so you won't think twice about using it even for mobile stuff). It does not require jQuery or any other framework to work. But it works great _with_ jQuery or Prototype or whatever else have you.
|
15
15
|
|
16
16
|
|
17
|
-
The page:
|
17
|
+
The `page:change` event
|
18
18
|
---------------------
|
19
19
|
|
20
|
-
Since pages will change without a full reload with Turbolinks, you can't by default rely on dom:loaded to trigger your JavaScript code. Instead, Turbolinks uses the page:
|
20
|
+
Since pages will change without a full reload with Turbolinks, you can't by default rely on `dom:loaded` to trigger your JavaScript code. Instead, Turbolinks uses the `page:change` event. Existing code will not be compatible with this style automatically, so it's your job to ensure that your initialization code will work with this change in events.
|
21
21
|
|
22
22
|
|
23
23
|
Triggering a Turbolinks visit manually
|
@@ -32,9 +32,17 @@ Available only for pushState browsers
|
|
32
32
|
Like pjax, this naturally only works with browsers capable of pushState. But of course we fall back gracefully to full page reloads for browsers that do not support it.
|
33
33
|
|
34
34
|
|
35
|
+
Installation
|
36
|
+
------------
|
37
|
+
|
38
|
+
1. Add `gem 'turbolinks'` to your Gemfile.
|
39
|
+
1. Run `bundle install`.
|
40
|
+
1. Add `//= require turbolinks` to your Javascript manifest file (usually found at `app/assets/javascripts/application.js`).
|
41
|
+
1. Restart your server and you're now using turbolinks!
|
42
|
+
|
43
|
+
|
35
44
|
Work left to do
|
36
45
|
---------------
|
37
46
|
|
38
47
|
* CSS/JS asset change detection and reload
|
39
|
-
* Add
|
40
|
-
* Remember scroll position when using back button
|
48
|
+
* Add proper unit tests
|
@@ -1,5 +1,10 @@
|
|
1
|
+
pageCache = []
|
2
|
+
currentState = null
|
3
|
+
initialized = false
|
4
|
+
|
1
5
|
visit = (url) ->
|
2
|
-
if browserSupportsPushState
|
6
|
+
if browserSupportsPushState
|
7
|
+
cacheCurrentPage()
|
3
8
|
reflectNewUrl url
|
4
9
|
fetchReplacement url
|
5
10
|
else
|
@@ -10,54 +15,110 @@ fetchReplacement = (url) ->
|
|
10
15
|
xhr = new XMLHttpRequest
|
11
16
|
xhr.open 'GET', url, true
|
12
17
|
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
13
|
-
xhr.onload = ->
|
14
|
-
xhr.onabort = -> console.log
|
18
|
+
xhr.onload = -> changePage extractTitleAndBody(xhr.responseText)...
|
19
|
+
xhr.onabort = -> console.log 'Aborted turbolink fetch!'
|
15
20
|
xhr.send()
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
fetchHistory = (state) ->
|
23
|
+
cacheCurrentPage()
|
24
|
+
|
25
|
+
if page = pageCache[state.position]
|
26
|
+
changePage page.title, page.body.cloneNode(true)
|
27
|
+
recallScrollPosition page
|
28
|
+
else
|
29
|
+
fetchReplacement document.location.href
|
30
|
+
|
31
|
+
|
32
|
+
cacheCurrentPage = ->
|
33
|
+
rememberInitialPage()
|
34
|
+
|
35
|
+
pageCache[currentState.position] =
|
36
|
+
url: document.location.href,
|
37
|
+
body: document.body,
|
38
|
+
title: document.title,
|
39
|
+
positionY: window.pageYOffset,
|
40
|
+
positionX: window.pageXOffset
|
41
|
+
|
42
|
+
constrainPageCacheTo(10)
|
43
|
+
|
44
|
+
constrainPageCacheTo = (limit) ->
|
45
|
+
delete pageCache[currentState.position - limit] if currentState.position == window.history.length - 1
|
46
|
+
|
47
|
+
|
48
|
+
changePage = (title, body) ->
|
49
|
+
document.title = title
|
50
|
+
document.documentElement.replaceChild body, document.body
|
51
|
+
|
52
|
+
currentState = window.history.state
|
53
|
+
triggerEvent 'page:change'
|
54
|
+
|
20
55
|
|
21
56
|
reflectNewUrl = (url) ->
|
22
|
-
|
57
|
+
if url isnt document.location.href
|
58
|
+
window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', url
|
59
|
+
|
60
|
+
rememberCurrentUrl = ->
|
61
|
+
window.history.replaceState { turbolinks: true, position: window.history.length - 1 }, '', document.location.href
|
62
|
+
|
63
|
+
rememberCurrentState = ->
|
64
|
+
currentState = window.history.state
|
23
65
|
|
24
|
-
|
66
|
+
rememberInitialPage = ->
|
67
|
+
unless initialized
|
68
|
+
rememberCurrentUrl()
|
69
|
+
rememberCurrentState()
|
70
|
+
initialized = true
|
71
|
+
|
72
|
+
recallScrollPosition = (page) ->
|
73
|
+
window.scrollTo page.positionX, page.positionX
|
74
|
+
|
75
|
+
|
76
|
+
triggerEvent = (name) ->
|
25
77
|
event = document.createEvent 'Events'
|
26
|
-
event.initEvent
|
78
|
+
event.initEvent name, true, true
|
27
79
|
document.dispatchEvent event
|
28
80
|
|
81
|
+
|
82
|
+
extractTitleAndBody = (html) ->
|
83
|
+
doc = createDocument html
|
84
|
+
title = doc.querySelector 'title'
|
85
|
+
[ title?.textContent, doc.body ]
|
86
|
+
|
29
87
|
createDocument = do ->
|
30
88
|
createDocumentUsingParser = (html) ->
|
31
|
-
(new DOMParser).parseFromString html,
|
89
|
+
(new DOMParser).parseFromString html, 'text/html'
|
32
90
|
|
33
91
|
createDocumentUsingWrite = (html) ->
|
34
|
-
doc = document.implementation.createHTMLDocument
|
35
|
-
doc.open
|
92
|
+
doc = document.implementation.createHTMLDocument ''
|
93
|
+
doc.open 'replace'
|
36
94
|
doc.write html
|
37
95
|
doc.close
|
38
96
|
doc
|
39
97
|
|
40
98
|
if window.DOMParser
|
41
|
-
testDoc = createDocumentUsingParser
|
99
|
+
testDoc = createDocumentUsingParser '<html><body><p>test'
|
42
100
|
|
43
101
|
if testDoc?.body?.childNodes.length is 1
|
44
102
|
createDocumentUsingParser
|
45
103
|
else
|
46
104
|
createDocumentUsingWrite
|
47
105
|
|
48
|
-
replaceHTML = (html) ->
|
49
|
-
doc = createDocument html
|
50
|
-
originalBody = document.body
|
51
|
-
document.documentElement.appendChild doc.body, originalBody
|
52
|
-
document.documentElement.removeChild originalBody
|
53
|
-
document.title = title.textContent if title = doc.querySelector "title"
|
54
106
|
|
107
|
+
handleClick = (event) ->
|
108
|
+
link = extractLink event
|
109
|
+
|
110
|
+
if link.nodeName is 'A' and !ignoreClick(event, link)
|
111
|
+
visit link.href
|
112
|
+
event.preventDefault()
|
55
113
|
|
56
114
|
extractLink = (event) ->
|
57
115
|
link = event.target
|
58
116
|
link = link.parentNode until link is document or link.nodeName is 'A'
|
59
117
|
link
|
60
118
|
|
119
|
+
samePageLink = (link) ->
|
120
|
+
link.href is document.location.href
|
121
|
+
|
61
122
|
crossOriginLink = (link) ->
|
62
123
|
location.protocol isnt link.protocol or location.host isnt link.host
|
63
124
|
|
@@ -66,7 +127,7 @@ anchoredLink = (link) ->
|
|
66
127
|
(link.href is location.href + '#')
|
67
128
|
|
68
129
|
nonHtmlLink = (link) ->
|
69
|
-
link.href.match(/\.[a-z]
|
130
|
+
link.href.match(/\.[a-z]+(\?.*)?$/g) and not link.href.match(/\.html?(\?.*)?$/g)
|
70
131
|
|
71
132
|
noTurbolink = (link) ->
|
72
133
|
link.getAttribute('data-no-turbolink')?
|
@@ -75,31 +136,19 @@ newTabClick = (event) ->
|
|
75
136
|
event.which > 1 or event.metaKey or event.ctrlKey
|
76
137
|
|
77
138
|
ignoreClick = (event, link) ->
|
78
|
-
|
139
|
+
samePageLink(link) or crossOriginLink(link) or anchoredLink(link) or
|
140
|
+
nonHtmlLink(link) or noTurbolink(link) or newTabClick(event)
|
79
141
|
|
80
|
-
handleClick = (event) ->
|
81
|
-
link = extractLink event
|
82
|
-
|
83
|
-
if link.nodeName is 'A' and !ignoreClick(event, link)
|
84
|
-
visit link.href
|
85
|
-
event.preventDefault()
|
86
|
-
|
87
|
-
|
88
|
-
browserSupportsPushState = window.history and window.history.pushState and window.history.replaceState
|
89
|
-
|
90
|
-
rememberInitialPage = ->
|
91
|
-
window.history.replaceState { turbolinks: true }, "", document.location.href
|
92
142
|
|
143
|
+
browserSupportsPushState =
|
144
|
+
window.history and window.history.pushState and window.history.replaceState
|
93
145
|
|
94
146
|
if browserSupportsPushState
|
95
|
-
rememberInitialPage()
|
96
|
-
|
97
147
|
window.addEventListener 'popstate', (event) ->
|
98
|
-
if event.state?.turbolinks
|
99
|
-
fetchReplacement document.location.href
|
148
|
+
fetchHistory event.state if event.state?.turbolinks
|
100
149
|
|
101
150
|
document.addEventListener 'click', (event) ->
|
102
151
|
handleClick event
|
103
152
|
|
104
153
|
# Call Turbolinks.visit(url) from client code
|
105
|
-
@Turbolinks =
|
154
|
+
@Turbolinks = visit: visit
|
data/test/config.ru
CHANGED
data/test/dummy.gif
ADDED
Binary file
|
data/test/index.html
CHANGED
@@ -10,11 +10,16 @@
|
|
10
10
|
});
|
11
11
|
</script>
|
12
12
|
</head>
|
13
|
-
<body>
|
14
|
-
<ul>
|
15
|
-
<li><a href="/
|
16
|
-
<li><a href="/
|
13
|
+
<body class="page-index">
|
14
|
+
<ul style="margin-top:200px;">
|
15
|
+
<li><a href="/other.html">Other page</a></li>
|
16
|
+
<li><a href="/other.html"><span>Wrapped link</span></a></li>
|
17
17
|
<li><a href="http://www.google.com/">Cross origin</a></li>
|
18
|
+
<li><a href="/dummy.gif?12345">Query Param Image Link</a></li>
|
19
|
+
<li><a href="#">Hash link</a></li>
|
18
20
|
</ul>
|
21
|
+
|
22
|
+
<div style="background:#ccc;height:5000px;width:200px;">
|
23
|
+
</div>
|
19
24
|
</body>
|
20
25
|
</html>
|
data/test/other.html
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbolinks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-27 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: david@loudthinking.com
|
@@ -22,6 +22,7 @@ files:
|
|
22
22
|
- README.md
|
23
23
|
- MIT-LICENSE
|
24
24
|
- test/config.ru
|
25
|
+
- test/dummy.gif
|
25
26
|
- test/index.html
|
26
27
|
- test/other.html
|
27
28
|
homepage:
|