Permalink
Browse files

Merge pull request #286 from smidwap/transition_cache

Transition cache
  • Loading branch information...
2 parents 1761351 + 4b68382 commit f57ac9ce747b385441c7633e6024226e2775226d @dhh dhh committed Jan 7, 2014
Showing with 75 additions and 27 deletions.
  1. +8 −0 CHANGELOG.md
  2. +16 −0 README.md
  3. +51 −27 lib/assets/javascripts/turbolinks.js.coffee
View
@@ -1,3 +1,11 @@
+## Turbolinks (master)
+
+* Add transition cache experimental feature. When enabled, cached copies of pages will be shown
+ before fetching and displaying a new copy from the server. A individual page can be opted-out
+ by adding `data-no-transition-cache` to any DOM element on the page.
+
+ *Matt De Leon*
+
## Turbolinks 2.1.0 (December 17, 2013)
* Improve browser support for `noscript` tag removal.
View
@@ -64,6 +64,22 @@ To implement a client-side spinner, you could listen for `page:fetch` to start i
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).
+Transition Cache: A Speed Boost
+-------------------------------------
+
+Transition Cache is an experimental feature that makes loading cached pages instanteneous. Once a user has visited a page, returning later to the page results in an instant load.
+
+For example, if Page A is already cached by Turbolinks and you are on Page B, clicking a link to Page A will *immediately* display the cached copy of Page A. Turbolinks will then fetch Page A from the server and replace the cached page once the new copy is returned.
+
+To enable Transition Cache, include the following in your javascript:
+```javascript
+Turbolinks.enableTransitionCache();
+```
+
+The one drawback is that dramatic differences in appearence between a cached copy and new copy may lead to a jarring affect for the end-user. This will be especially true for pages that have many moving parts (expandable sections, sortable tables, infinite scrolling, etc.).
+
+If you find that a page is causing problems, you can have Turbolinks skip displaying the cached copy by adding `data-no-transition-cache` to any DOM element on the offending page.
+
Initialization
--------------
@@ -1,18 +1,36 @@
-pageCache = {}
-cacheSize = 10
-currentState = null
-loadedAssets = null
-htmlExtensions = ['html']
+pageCache = {}
+cacheSize = 10
+transitionCacheEnabled = false
-referer = null
+currentState = null
+loadedAssets = null
+htmlExtensions = ['html']
-createDocument = null
-xhr = null
+referer = null
+createDocument = null
+xhr = null
-fetchReplacement = (url) ->
+
+fetch = (url) ->
rememberReferer()
cacheCurrentPage()
+ reflectNewUrl url
+
+ if transitionCacheEnabled and cachedPage = transitionCacheFor(url)
+ fetchHistory cachedPage
+ fetchReplacement url
+ else
+ fetchReplacement url, resetScrollPosition
+
+transitionCacheFor = (url) ->
+ cachedPage = pageCache[url]
+ cachedPage if cachedPage and !cachedPage.transitionCacheDisabled
+
+enableTransitionCache = (enable = true) ->
+ transitionCacheEnabled = enable
+
+fetchReplacement = (url, onLoadFunction = =>) ->
triggerEvent 'page:fetch', url: url
xhr?.abort()
@@ -25,46 +43,50 @@ fetchReplacement = (url) ->
triggerEvent 'page:receive'
if doc = processResponse()
- reflectNewUrl url
changePage extractTitleAndBody(doc)...
reflectRedirectedUrl()
- resetScrollPosition()
+ onLoadFunction()
triggerEvent 'page:load'
else
document.location.href = url
xhr.onloadend = -> xhr = null
- xhr.onabort = -> rememberCurrentUrl()
xhr.onerror = -> document.location.href = url
xhr.send()
fetchHistory = (cachedPage) ->
- cacheCurrentPage()
xhr?.abort()
changePage cachedPage.title, cachedPage.body
recallScrollPosition cachedPage
triggerEvent 'page:restore'
cacheCurrentPage = ->
- pageCache[currentState.position] =
- url: document.location.href,
- body: document.body,
- title: document.title,
- positionY: window.pageYOffset,
- positionX: window.pageXOffset
+ pageCache[currentState.url] =
+ url: document.location.href,
+ body: document.body,
+ title: document.title,
+ positionY: window.pageYOffset,
+ positionX: window.pageXOffset,
+ cachedAt: new Date().getTime(),
+ transitionCacheDisabled: document.querySelector('[data-no-transition-cache]')?
constrainPageCacheTo cacheSize
pagesCached = (size = cacheSize) ->
cacheSize = parseInt(size) if /^[\d]+$/.test size
constrainPageCacheTo = (limit) ->
- for own key, value of pageCache when key <= currentState.position - limit
+ pageCacheKeys = Object.keys pageCache
+
+ cacheTimesRecentFirst = pageCacheKeys.map (url) ->
+ pageCache[url].cachedAt
+ .sort (a, b) -> b - a
+
+ for key in pageCacheKeys when pageCache[key].cachedAt <= cacheTimesRecentFirst[limit]
triggerEvent 'page:expire', pageCache[key]
- pageCache[key] = null
- return
+ delete pageCache[key]
changePage = (title, body, csrfToken, runScripts) ->
document.title = title
@@ -92,7 +114,7 @@ removeNoscriptTags = (node) ->
reflectNewUrl = (url) ->
if url isnt referer
- window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', url
+ window.history.pushState { turbolinks: true, url: url }, '', url
reflectRedirectedUrl = ->
if location = xhr.getResponseHeader 'X-XHR-Redirected-To'
@@ -103,7 +125,7 @@ rememberReferer = ->
referer = document.location.href
rememberCurrentUrl = ->
- window.history.replaceState { turbolinks: true, position: Date.now() }, '', document.location.href
+ window.history.replaceState { turbolinks: true, url: document.location.href }, '', document.location.href
rememberCurrentState = ->
currentState = window.history.state
@@ -282,7 +304,8 @@ installJqueryAjaxSuccessPageUpdateTrigger = ->
installHistoryChangeHandler = (event) ->
if event.state?.turbolinks
- if cachedPage = pageCache[event.state.position]
+ if cachedPage = pageCache[event.state.url]
+ cacheCurrentPage()
fetchHistory cachedPage
else
visit event.target.location.href
@@ -318,7 +341,7 @@ if browserSupportsCustomEvents
installJqueryAjaxSuccessPageUpdateTrigger()
if browserSupportsTurbolinks
- visit = fetchReplacement
+ visit = fetch
initializeTurbolinks()
else
visit = (url) -> document.location.href = url
@@ -327,6 +350,7 @@ else
# Turbolinks.visit(url)
# Turbolinks.pagesCached()
# Turbolinks.pagesCached(20)
+# Turbolinks.enableTransitionCache()
# Turbolinks.allowLinkExtensions('md')
# Turbolinks.supported
-@Turbolinks = { visit, pagesCached, allowLinkExtensions, supported: browserSupportsTurbolinks }
+@Turbolinks = { visit, pagesCached, enableTransitionCache, allowLinkExtensions, supported: browserSupportsTurbolinks }

0 comments on commit f57ac9c

Please sign in to comment.