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 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', url, true
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
- resetScrollPosition()
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.onabort = -> console.log 'Aborted turbolink fetch!'
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
- if script.src? and script.src isnt '' and not script.getAttribute('data-turbolinks-evaluated')?
77
- copy = document.createElement 'script'
78
- copy.setAttribute attr.name, attr.value for attr in script.attributes
79
- copy.setAttribute 'data-turbolinks-evaluated', ''
80
- parent = script.parentNode
81
- parent.removeChild script
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
- unless (location = xhr.getResponseHeader 'X-XHR-Current-Location') is document.location.pathname + document.location.search
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?.nodeName is 'A' and !ignoreClick(event, 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 link.href.replace(link.hash, '')) is location.href.replace(location.hash, '')) or
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
- if browserSupportsPushState
206
- document.addEventListener 'click', installClickHandlerLast, true
227
+ browserIsntBuggy =
228
+ !navigator.userAgent.match /CriOS\//
207
229
 
208
- window.addEventListener 'popstate', (event) ->
209
- fetchHistory event.state if event.state?.turbolinks
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 }
@@ -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
@@ -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>
@@ -0,0 +1,10 @@
1
+ CACHE MANIFEST
2
+
3
+ CACHE:
4
+ /offline.html
5
+
6
+ NETWORK:
7
+ *
8
+
9
+ FALLBACK:
10
+ /fallback.html /offline.html
@@ -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: 2012-12-04 00:00:00.000000000 Z
12
+ date: 2013-01-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- version_requirements: !ruby/object:Gem::Requirement
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
- requirement: !ruby/object:Gem::Requirement
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