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 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, and potentially spend extra HTTP requests checking if the assets are up-to-date, we keep the current instance alive and replace only the body and the title in the head. Think CGI vs persistent process.
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:update event
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:update event.
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 a DOM cache for faster back button
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 = -> fullReplacement xhr.responseText, url
14
- xhr.onabort = -> console.log "Aborted turbolink fetch!"
18
+ xhr.onload = -> changePage extractTitleAndBody(xhr.responseText)...
19
+ xhr.onabort = -> console.log 'Aborted turbolink fetch!'
15
20
  xhr.send()
16
21
 
17
- fullReplacement = (html, url) ->
18
- replaceHTML html
19
- triggerPageChange()
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
- window.history.pushState { turbolinks: true }, "", url
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
- triggerPageChange = ->
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 'page:change', true, true
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, "text/html"
89
+ (new DOMParser).parseFromString html, 'text/html'
32
90
 
33
91
  createDocumentUsingWrite = (html) ->
34
- doc = document.implementation.createHTMLDocument ""
35
- doc.open "replace"
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 "<html><body><p>test"
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]+$/g) and not link.href.match(/\.html?$/g)
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
- crossOriginLink(link) or anchoredLink(link) or nonHtmlLink(link) or noTurbolink(link) or newTabClick(event)
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 = { visit: visit }
154
+ @Turbolinks = visit: visit
@@ -11,4 +11,4 @@ map "/js" do
11
11
  run Assets
12
12
  end
13
13
 
14
- run Rack::Directory.new(Root)
14
+ run Rack::Directory.new(File.join(Root, "test"))
Binary file
@@ -10,11 +10,16 @@
10
10
  });
11
11
  </script>
12
12
  </head>
13
- <body>
14
- <ul>
15
- <li><a href="/test/other.html">Other page</a></li>
16
- <li><a href="/test/other.html"><span>Wrapped link</span></a></li>
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>
@@ -10,9 +10,9 @@
10
10
  });
11
11
  </script>
12
12
  </head>
13
- <body>
13
+ <body class="page-other">
14
14
  <ul>
15
- <li><a href="/test/index.html">Home</a></li>
15
+ <li><a href="/index.html">Home</a></li>
16
16
  </ul>
17
17
  </body>
18
18
  </html>
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.5
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-26 00:00:00.000000000 Z
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: