turbograft 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/assets/javascripts/click.coffee +37 -0
- data/lib/assets/javascripts/component_url.coffee +24 -0
- data/lib/assets/javascripts/csrf_token.coffee +9 -0
- data/lib/assets/javascripts/initializers.coffee +16 -0
- data/lib/assets/javascripts/link.coffee +41 -0
- data/lib/assets/javascripts/page.coffee +25 -0
- data/lib/assets/javascripts/page_cache.coffee +42 -0
- data/lib/assets/javascripts/turbograft.coffee +8 -0
- data/lib/assets/javascripts/turbolinks.coffee +353 -0
- data/lib/turbograft/version.rb +1 -1
- data/lib/turbograft.js +991 -0
- metadata +41 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c249b4ccd0c6e283c8a5bf909ebdec4541b4f4d
|
4
|
+
data.tar.gz: 16169d007d25ecc6ac97c05fb604047440a4ec78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a843ee131ba65714567e500cba001ec560ac8459316c8f146bb30de45145eb9831a1f5ac69a102a573b667ddff50ead11e2b513b300ddc0487e977888418483
|
7
|
+
data.tar.gz: 40ffcb9804a953630b2f58a4f7d909ba3616abdfcf391e3f742ff0bd181dc0e0c9e20e709732a5078b418e3f870ab6b14a7c6fe576884966a276e1c014e8b4a5
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# The Click class handles clicked links, verifying if Turbolinks should
|
2
|
+
# take control by inspecting both the event and the link. If it should,
|
3
|
+
# the page change process is initiated. If not, control is passed back
|
4
|
+
# to the browser for default functionality.
|
5
|
+
class window.Click
|
6
|
+
@installHandlerLast: (event) ->
|
7
|
+
unless event.defaultPrevented
|
8
|
+
document.removeEventListener 'click', Click.handle, false
|
9
|
+
document.addEventListener 'click', Click.handle, false
|
10
|
+
|
11
|
+
@handle: (event) ->
|
12
|
+
new Click event
|
13
|
+
|
14
|
+
constructor: (@event) ->
|
15
|
+
return if @event.defaultPrevented
|
16
|
+
@_extractLink()
|
17
|
+
if @_validForTurbolinks()
|
18
|
+
Turbolinks.visit @link.href unless @_pageChangePrevented()
|
19
|
+
@event.preventDefault()
|
20
|
+
|
21
|
+
_pageChangePrevented: ->
|
22
|
+
!triggerEvent 'page:before-change' # TODO: fix this global
|
23
|
+
|
24
|
+
_extractLink: ->
|
25
|
+
link = @event.target
|
26
|
+
link = link.parentNode until !link.parentNode or link.nodeName is 'A'
|
27
|
+
@link = new Link(link) if link.nodeName is 'A' and link.href.length isnt 0
|
28
|
+
|
29
|
+
_validForTurbolinks: ->
|
30
|
+
@link? and not (@link.shouldIgnore() or @_nonStandardClick())
|
31
|
+
|
32
|
+
_nonStandardClick: ->
|
33
|
+
@event.which > 1 or
|
34
|
+
@event.metaKey or
|
35
|
+
@event.ctrlKey or
|
36
|
+
@event.shiftKey or
|
37
|
+
@event.altKey
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# The ComponentUrl class converts a basic URL string into an object
|
2
|
+
# that behaves similarly to document.location.
|
3
|
+
#
|
4
|
+
# If an instance is created from a relative URL, the current document
|
5
|
+
# is used to fill in the missing attributes (protocol, host, port).
|
6
|
+
class window.ComponentUrl
|
7
|
+
constructor: (@original = document.location.href) ->
|
8
|
+
return @original if @original.constructor is ComponentUrl
|
9
|
+
@_parse()
|
10
|
+
|
11
|
+
withoutHash: -> @href.replace @hash, ''
|
12
|
+
|
13
|
+
# Intention revealing function alias
|
14
|
+
withoutHashForIE10compatibility: -> @withoutHash()
|
15
|
+
|
16
|
+
hasNoHash: -> @hash.length is 0
|
17
|
+
|
18
|
+
_parse: ->
|
19
|
+
(@link ?= document.createElement 'a').href = @original
|
20
|
+
{ @href, @protocol, @host, @hostname, @port, @pathname, @search, @hash } = @link
|
21
|
+
@origin = [@protocol, '//', @hostname].join ''
|
22
|
+
@origin += ":#{@port}" unless @port.length is 0
|
23
|
+
@relative = [@pathname, @search, @hash].join ''
|
24
|
+
@absolute = @href
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class window.CSRFToken
|
2
|
+
@get: (doc = document) ->
|
3
|
+
node: tag = doc.querySelector 'meta[name="csrf-token"]'
|
4
|
+
token: tag?.getAttribute? 'content'
|
5
|
+
|
6
|
+
@update: (latest) ->
|
7
|
+
current = @get()
|
8
|
+
if current.token? and latest? and current.token isnt latest
|
9
|
+
current.node.setAttribute 'content', latest
|
@@ -0,0 +1,16 @@
|
|
1
|
+
partialGraftClickHandler = (ev) ->
|
2
|
+
target = ev.target
|
3
|
+
partialGraft = target.getAttribute("partial-graft")
|
4
|
+
return unless partialGraft?
|
5
|
+
href = target.getAttribute("href")
|
6
|
+
refresh = target.getAttribute("refresh")
|
7
|
+
throw "TurboGraft developer error: href is not defined on node #{target}" if !href?
|
8
|
+
throw "TurboGraft developer error: refresh is not defined on node #{target}" if !refresh?
|
9
|
+
|
10
|
+
keys = refresh.trim().split(" ")
|
11
|
+
|
12
|
+
Page.refresh
|
13
|
+
url: href,
|
14
|
+
onlyKeys: keys
|
15
|
+
|
16
|
+
document.addEventListener 'click', partialGraftClickHandler, true
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# The Link class derives from the ComponentUrl class, but is built from an
|
2
|
+
# existing link element. Provides verification functionality for Turbolinks
|
3
|
+
# to use in determining whether it should process the link when clicked.
|
4
|
+
class window.Link extends ComponentUrl
|
5
|
+
@HTML_EXTENSIONS: ['html']
|
6
|
+
|
7
|
+
@allowExtensions: (extensions...) ->
|
8
|
+
Link.HTML_EXTENSIONS.push extension for extension in extensions
|
9
|
+
Link.HTML_EXTENSIONS
|
10
|
+
|
11
|
+
constructor: (@link) ->
|
12
|
+
return @link if @link.constructor is Link
|
13
|
+
@original = @link.href
|
14
|
+
super
|
15
|
+
|
16
|
+
shouldIgnore: ->
|
17
|
+
@_crossOrigin() or
|
18
|
+
@_anchored() or
|
19
|
+
@_nonHtml() or
|
20
|
+
@_optOut() or
|
21
|
+
@_target()
|
22
|
+
|
23
|
+
_crossOrigin: ->
|
24
|
+
@origin isnt (new ComponentUrl).origin
|
25
|
+
|
26
|
+
_anchored: ->
|
27
|
+
((@hash and @withoutHash()) is (current = new ComponentUrl).withoutHash()) or
|
28
|
+
(@href is current.href + '#')
|
29
|
+
|
30
|
+
_nonHtml: ->
|
31
|
+
@pathname.match(/\.[a-z]+$/g) and not @pathname.match(new RegExp("\\.(?:#{Link.HTML_EXTENSIONS.join('|')})?$", 'g'))
|
32
|
+
|
33
|
+
_optOut: ->
|
34
|
+
link = @link
|
35
|
+
until ignore or link is document or link is null
|
36
|
+
ignore = link.getAttribute('data-no-turbolink')?
|
37
|
+
link = link.parentNode
|
38
|
+
ignore
|
39
|
+
|
40
|
+
_target: ->
|
41
|
+
@link.target.length isnt 0
|
@@ -0,0 +1,25 @@
|
|
1
|
+
window.Page = {} if !window.Page
|
2
|
+
|
3
|
+
Page.visit = (url, opts={}) ->
|
4
|
+
if opts.reload
|
5
|
+
window.location = url
|
6
|
+
else
|
7
|
+
Turbolinks.visit(url)
|
8
|
+
|
9
|
+
Page.refresh = (options = {}, callback) ->
|
10
|
+
newUrl = if options.url
|
11
|
+
options.url
|
12
|
+
else if options.queryParams
|
13
|
+
paramString = $.param(options.queryParams)
|
14
|
+
paramString = "?#{paramString}" if paramString
|
15
|
+
location.pathname + paramString
|
16
|
+
else
|
17
|
+
location.href
|
18
|
+
|
19
|
+
if options.response
|
20
|
+
Turbolinks.loadPage null, options.response, true, callback, options.onlyKeys || []
|
21
|
+
else
|
22
|
+
Turbolinks.visit newUrl, true, options.onlyKeys || [], -> callback?()
|
23
|
+
|
24
|
+
Page.open = ->
|
25
|
+
window.open(arguments...)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class window.PageCache
|
2
|
+
storage = {}
|
3
|
+
simultaneousAdditionOffset = 0
|
4
|
+
constructor: (@cacheSize = 10) ->
|
5
|
+
storage = {}
|
6
|
+
return this
|
7
|
+
|
8
|
+
get: (key) ->
|
9
|
+
storage[key]
|
10
|
+
|
11
|
+
set: (key, value) ->
|
12
|
+
if typeof value != "object"
|
13
|
+
throw "Developer error: You must store objects in this cache"
|
14
|
+
|
15
|
+
value['cachedAt'] = new Date().getTime() + (simultaneousAdditionOffset+=1)
|
16
|
+
|
17
|
+
storage[key] = value
|
18
|
+
@constrain()
|
19
|
+
|
20
|
+
clear: ->
|
21
|
+
storage = {}
|
22
|
+
|
23
|
+
setCacheSize: (newSize) ->
|
24
|
+
if /^[\d]+$/.test(newSize)
|
25
|
+
@cacheSize = parseInt(newSize, 10)
|
26
|
+
@constrain()
|
27
|
+
else
|
28
|
+
throw "Developer error: Invalid parameter '#{newSize}' for PageCache; must be integer"
|
29
|
+
|
30
|
+
constrain: ->
|
31
|
+
pageCacheKeys = Object.keys storage
|
32
|
+
|
33
|
+
cacheTimesRecentFirst = pageCacheKeys.map (url) =>
|
34
|
+
storage[url].cachedAt
|
35
|
+
.sort (a, b) -> b - a
|
36
|
+
|
37
|
+
for key in pageCacheKeys when storage[key].cachedAt <= cacheTimesRecentFirst[@cacheSize]
|
38
|
+
triggerEvent 'page:expire', storage[key] # TODO: fix this global
|
39
|
+
delete storage[key]
|
40
|
+
|
41
|
+
length: ->
|
42
|
+
Object.keys(storage).length
|
@@ -0,0 +1,353 @@
|
|
1
|
+
xhr = null
|
2
|
+
|
3
|
+
installDocumentReadyPageEventTriggers = ->
|
4
|
+
document.addEventListener 'DOMContentLoaded', ( ->
|
5
|
+
triggerEvent 'page:change'
|
6
|
+
triggerEvent 'page:update'
|
7
|
+
), true
|
8
|
+
|
9
|
+
installJqueryAjaxSuccessPageUpdateTrigger = ->
|
10
|
+
if typeof jQuery isnt 'undefined'
|
11
|
+
jQuery(document).on 'ajaxSuccess', (event, xhr, settings) ->
|
12
|
+
return unless jQuery.trim xhr.responseText
|
13
|
+
triggerEvent 'page:update'
|
14
|
+
|
15
|
+
# Handle bug in Firefox 26/27 where history.state is initially undefined
|
16
|
+
historyStateIsDefined =
|
17
|
+
window.history.state != undefined or navigator.userAgent.match /Firefox\/2[6|7]/
|
18
|
+
|
19
|
+
browserSupportsPushState =
|
20
|
+
window.history and window.history.pushState and window.history.replaceState and historyStateIsDefined
|
21
|
+
|
22
|
+
browserIsntBuggy =
|
23
|
+
!navigator.userAgent.match /CriOS\//
|
24
|
+
|
25
|
+
window.triggerEvent = (name, data) ->
|
26
|
+
event = document.createEvent 'Events'
|
27
|
+
event.data = data if data
|
28
|
+
event.initEvent name, true, true
|
29
|
+
document.dispatchEvent event
|
30
|
+
|
31
|
+
popCookie = (name) ->
|
32
|
+
value = document.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or ''
|
33
|
+
document.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'
|
34
|
+
value
|
35
|
+
|
36
|
+
requestMethodIsSafe =
|
37
|
+
popCookie('request_method') in ['GET','']
|
38
|
+
|
39
|
+
browserSupportsTurbolinks = browserSupportsPushState and browserIsntBuggy and requestMethodIsSafe
|
40
|
+
|
41
|
+
browserSupportsCustomEvents =
|
42
|
+
document.addEventListener and document.createEvent
|
43
|
+
|
44
|
+
if browserSupportsCustomEvents
|
45
|
+
installDocumentReadyPageEventTriggers()
|
46
|
+
installJqueryAjaxSuccessPageUpdateTrigger()
|
47
|
+
|
48
|
+
# TODO: triggerEvent should be accessible to all these guys
|
49
|
+
# on some kind of eventbus
|
50
|
+
# TODO: clean up everything above me ^
|
51
|
+
# TODO: decide on the public API
|
52
|
+
class window.Turbolinks
|
53
|
+
createDocument = null
|
54
|
+
currentState = null
|
55
|
+
loadedAssets = null
|
56
|
+
referer = null
|
57
|
+
usePageCache = false
|
58
|
+
@pageCache = pageCache = new PageCache()
|
59
|
+
|
60
|
+
fetch = (url, partialReplace = false, replaceContents = [], callback) ->
|
61
|
+
url = new ComponentUrl url
|
62
|
+
|
63
|
+
rememberReferer()
|
64
|
+
Turbolinks.cacheCurrentPage() if usePageCache
|
65
|
+
|
66
|
+
if usePageCache and cachedPage = transitionCacheFor(url.absolute)
|
67
|
+
fetchHistory cachedPage
|
68
|
+
fetchReplacement url, partialReplace, null, replaceContents
|
69
|
+
else
|
70
|
+
fetchReplacement url, partialReplace, ->
|
71
|
+
resetScrollPosition() unless replaceContents.length
|
72
|
+
callback?()
|
73
|
+
, replaceContents
|
74
|
+
|
75
|
+
@pageCacheEnabled = ->
|
76
|
+
usePageCache
|
77
|
+
|
78
|
+
@usePageCache = (status) ->
|
79
|
+
usePageCache = status
|
80
|
+
|
81
|
+
transitionCacheFor = (url) ->
|
82
|
+
cachedPage = pageCache.get(url)
|
83
|
+
cachedPage if cachedPage and !cachedPage.transitionCacheDisabled
|
84
|
+
|
85
|
+
@pushState: (state, title, url) ->
|
86
|
+
window.history.pushState(state, title, url)
|
87
|
+
|
88
|
+
@replaceState: (state, title, url) ->
|
89
|
+
window.history.replaceState(state, title, url)
|
90
|
+
|
91
|
+
fetchReplacement = (url, partialReplace, onLoadFunction, replaceContents) ->
|
92
|
+
triggerEvent 'page:fetch', url: url.absolute
|
93
|
+
|
94
|
+
xhr?.abort()
|
95
|
+
xhr = new XMLHttpRequest
|
96
|
+
xhr.open 'GET', url.withoutHashForIE10compatibility(), true
|
97
|
+
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
98
|
+
xhr.setRequestHeader 'X-XHR-Referer', referer
|
99
|
+
|
100
|
+
xhr.onload = ->
|
101
|
+
if xhr.status >= 500
|
102
|
+
document.location.href = url.absolute
|
103
|
+
else
|
104
|
+
Turbolinks.loadPage(url, xhr, partialReplace, onLoadFunction, replaceContents)
|
105
|
+
|
106
|
+
xhr.onloadend = -> xhr = null
|
107
|
+
xhr.onerror = ->
|
108
|
+
document.location.href = url.absolute
|
109
|
+
|
110
|
+
xhr.send()
|
111
|
+
|
112
|
+
return
|
113
|
+
|
114
|
+
@loadPage: (url, xhr, partialReplace = false, onLoadFunction = (->), replaceContents = []) ->
|
115
|
+
triggerEvent 'page:receive'
|
116
|
+
|
117
|
+
if doc = processResponse(xhr, partialReplace)
|
118
|
+
reflectNewUrl url
|
119
|
+
nodes = changePage(extractTitleAndBody(doc)..., partialReplace, replaceContents)
|
120
|
+
reflectRedirectedUrl(xhr)
|
121
|
+
triggerEvent 'page:load', nodes
|
122
|
+
onLoadFunction?()
|
123
|
+
else
|
124
|
+
document.location.href = url.absolute
|
125
|
+
|
126
|
+
return
|
127
|
+
|
128
|
+
fetchHistory = (cachedPage) ->
|
129
|
+
xhr?.abort()
|
130
|
+
changePage cachedPage.title, cachedPage.body, false
|
131
|
+
recallScrollPosition cachedPage
|
132
|
+
triggerEvent 'page:restore'
|
133
|
+
|
134
|
+
|
135
|
+
@cacheCurrentPage: ->
|
136
|
+
currentStateUrl = new ComponentUrl currentState.url
|
137
|
+
|
138
|
+
pageCache.set currentStateUrl.absolute,
|
139
|
+
url: currentStateUrl.relative,
|
140
|
+
body: document.body,
|
141
|
+
title: document.title,
|
142
|
+
positionY: window.pageYOffset,
|
143
|
+
positionX: window.pageXOffset,
|
144
|
+
transitionCacheDisabled: document.querySelector('[data-no-transition-cache]')?
|
145
|
+
|
146
|
+
return
|
147
|
+
|
148
|
+
changePage = (title, body, csrfToken, runScripts, partialReplace, replaceContents = []) ->
|
149
|
+
document.title = title if title
|
150
|
+
if replaceContents.length
|
151
|
+
return refreshNodesWithKeys(replaceContents, body)
|
152
|
+
else
|
153
|
+
deleteRefreshNeverNodes(body)
|
154
|
+
|
155
|
+
triggerEvent 'page:before-replace'
|
156
|
+
document.documentElement.replaceChild body, document.body
|
157
|
+
CSRFToken.update csrfToken if csrfToken?
|
158
|
+
executeScriptTags() if runScripts
|
159
|
+
currentState = window.history.state
|
160
|
+
triggerEvent 'page:change'
|
161
|
+
triggerEvent 'page:update'
|
162
|
+
|
163
|
+
return
|
164
|
+
|
165
|
+
deleteRefreshNeverNodes = (body) ->
|
166
|
+
for node in body.querySelectorAll('[refresh-never]')
|
167
|
+
node.parentNode.removeChild(node)
|
168
|
+
|
169
|
+
return
|
170
|
+
|
171
|
+
refreshNodesWithKeys = (keys, body) ->
|
172
|
+
allNodesToBeRefreshed = []
|
173
|
+
for node in document.querySelectorAll("[refresh-always]")
|
174
|
+
allNodesToBeRefreshed.push(node)
|
175
|
+
|
176
|
+
for key in keys
|
177
|
+
for node in document.querySelectorAll("[refresh=#{key}]")
|
178
|
+
allNodesToBeRefreshed.push(node)
|
179
|
+
|
180
|
+
triggerEvent 'page:before-partial-replace', allNodesToBeRefreshed
|
181
|
+
|
182
|
+
parentIsRefreshing = (node) ->
|
183
|
+
for potentialParent in allNodesToBeRefreshed when node != potentialParent
|
184
|
+
return true if potentialParent.contains(node)
|
185
|
+
false
|
186
|
+
|
187
|
+
refreshedNodes = []
|
188
|
+
for existingNode in allNodesToBeRefreshed
|
189
|
+
continue if parentIsRefreshing(existingNode)
|
190
|
+
|
191
|
+
unless nodeId = existingNode.getAttribute('id')
|
192
|
+
throw new Error "Turbolinks refresh: Refresh key elements must have an id."
|
193
|
+
|
194
|
+
if newNode = body.querySelector("##{ nodeId }")
|
195
|
+
existingNode.parentNode.replaceChild(newNode, existingNode)
|
196
|
+
|
197
|
+
if newNode.nodeName == 'SCRIPT' && newNode.getAttribute("data-turbolinks-eval") != "false"
|
198
|
+
executeScriptTag(newNode)
|
199
|
+
else
|
200
|
+
refreshedNodes.push(newNode)
|
201
|
+
|
202
|
+
else if existingNode.getAttribute("refresh-always") == null
|
203
|
+
existingNode.parentNode.removeChild(existingNode)
|
204
|
+
|
205
|
+
refreshedNodes
|
206
|
+
|
207
|
+
executeScriptTags = ->
|
208
|
+
scripts = Array::slice.call document.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])'
|
209
|
+
for script in scripts when script.type in ['', 'text/javascript']
|
210
|
+
executeScriptTag(script)
|
211
|
+
return
|
212
|
+
|
213
|
+
executeScriptTag = (script) ->
|
214
|
+
copy = document.createElement 'script'
|
215
|
+
copy.setAttribute attr.name, attr.value for attr in script.attributes
|
216
|
+
copy.appendChild document.createTextNode script.innerHTML
|
217
|
+
{ parentNode, nextSibling } = script
|
218
|
+
parentNode.removeChild script
|
219
|
+
parentNode.insertBefore copy, nextSibling
|
220
|
+
return
|
221
|
+
|
222
|
+
removeNoscriptTags = (node) ->
|
223
|
+
node.innerHTML = node.innerHTML.replace /<noscript[\S\s]*?<\/noscript>/ig, ''
|
224
|
+
node
|
225
|
+
|
226
|
+
reflectNewUrl = (url) ->
|
227
|
+
if (url = new ComponentUrl url).absolute isnt referer
|
228
|
+
Turbolinks.pushState { turbolinks: true, url: url.absolute }, '', url.absolute
|
229
|
+
return
|
230
|
+
|
231
|
+
reflectRedirectedUrl = (xhr) ->
|
232
|
+
if location = xhr.getResponseHeader 'X-XHR-Redirected-To'
|
233
|
+
location = new ComponentUrl location
|
234
|
+
preservedHash = if location.hasNoHash() then document.location.hash else ''
|
235
|
+
Turbolinks.replaceState currentState, '', location.href + preservedHash
|
236
|
+
return
|
237
|
+
|
238
|
+
rememberReferer = ->
|
239
|
+
referer = document.location.href
|
240
|
+
|
241
|
+
@rememberCurrentUrl: ->
|
242
|
+
Turbolinks.replaceState { turbolinks: true, url: document.location.href }, '', document.location.href
|
243
|
+
|
244
|
+
@rememberCurrentState: ->
|
245
|
+
currentState = window.history.state
|
246
|
+
|
247
|
+
recallScrollPosition = (page) ->
|
248
|
+
window.scrollTo page.positionX, page.positionY
|
249
|
+
|
250
|
+
resetScrollPosition = ->
|
251
|
+
if document.location.hash
|
252
|
+
document.location.href = document.location.href
|
253
|
+
else
|
254
|
+
window.scrollTo 0, 0
|
255
|
+
|
256
|
+
pageChangePrevented = ->
|
257
|
+
!triggerEvent 'page:before-change'
|
258
|
+
|
259
|
+
processResponse = (xhr, partial = false) ->
|
260
|
+
clientOrServerError = ->
|
261
|
+
return false if xhr.status == 422 # we want to render form validations
|
262
|
+
400 <= xhr.status < 600
|
263
|
+
|
264
|
+
validContent = ->
|
265
|
+
xhr.getResponseHeader('Content-Type').match /^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/
|
266
|
+
|
267
|
+
extractTrackAssets = (doc) ->
|
268
|
+
for node in doc.head.childNodes when node.getAttribute?('data-turbolinks-track')?
|
269
|
+
node.getAttribute('src') or node.getAttribute('href')
|
270
|
+
|
271
|
+
assetsChanged = (doc) ->
|
272
|
+
loadedAssets ||= extractTrackAssets document
|
273
|
+
fetchedAssets = extractTrackAssets doc
|
274
|
+
fetchedAssets.length isnt loadedAssets.length or intersection(fetchedAssets, loadedAssets).length isnt loadedAssets.length
|
275
|
+
|
276
|
+
intersection = (a, b) ->
|
277
|
+
[a, b] = [b, a] if a.length > b.length
|
278
|
+
value for value in a when value in b
|
279
|
+
|
280
|
+
if !clientOrServerError() && validContent()
|
281
|
+
doc = createDocument xhr.responseText
|
282
|
+
changed = assetsChanged(doc)
|
283
|
+
|
284
|
+
if doc && (!changed || partial)
|
285
|
+
return doc
|
286
|
+
|
287
|
+
extractTitleAndBody = (doc) ->
|
288
|
+
title = doc.querySelector 'title'
|
289
|
+
[ title?.textContent, removeNoscriptTags(doc.body), CSRFToken.get(doc).token, 'runScripts' ]
|
290
|
+
|
291
|
+
installHistoryChangeHandler = (event) ->
|
292
|
+
if event.state?.turbolinks
|
293
|
+
if cachedPage = pageCache.get((new ComponentUrl(event.state.url)).absolute)
|
294
|
+
Turbolinks.cacheCurrentPage()
|
295
|
+
fetchHistory cachedPage
|
296
|
+
else
|
297
|
+
Turbolinks.visit event.target.location.href
|
298
|
+
|
299
|
+
# Delay execution of function long enough to miss the popstate event
|
300
|
+
# some browsers fire on the initial page load.
|
301
|
+
bypassOnLoadPopstate = (fn) ->
|
302
|
+
setTimeout fn, 500
|
303
|
+
|
304
|
+
browserCompatibleDocumentParser = ->
|
305
|
+
createDocumentUsingParser = (html) ->
|
306
|
+
(new DOMParser).parseFromString html, 'text/html'
|
307
|
+
|
308
|
+
createDocumentUsingDOM = (html) ->
|
309
|
+
doc = document.implementation.createHTMLDocument ''
|
310
|
+
doc.documentElement.innerHTML = html
|
311
|
+
doc
|
312
|
+
|
313
|
+
createDocumentUsingWrite = (html) ->
|
314
|
+
doc = document.implementation.createHTMLDocument ''
|
315
|
+
doc.open 'replace'
|
316
|
+
doc.write html
|
317
|
+
doc.close()
|
318
|
+
doc
|
319
|
+
|
320
|
+
# Use createDocumentUsingParser if DOMParser is defined and natively
|
321
|
+
# supports 'text/html' parsing (Firefox 12+, IE 10)
|
322
|
+
#
|
323
|
+
# Use createDocumentUsingDOM if createDocumentUsingParser throws an exception
|
324
|
+
# due to unsupported type 'text/html' (Firefox < 12, Opera)
|
325
|
+
#
|
326
|
+
# Use createDocumentUsingWrite if:
|
327
|
+
# - DOMParser isn't defined
|
328
|
+
# - createDocumentUsingParser returns null due to unsupported type 'text/html' (Chrome, Safari)
|
329
|
+
# - createDocumentUsingDOM doesn't create a valid HTML document (safeguarding against potential edge cases)
|
330
|
+
try
|
331
|
+
if window.DOMParser
|
332
|
+
testDoc = createDocumentUsingParser '<html><body><p>test'
|
333
|
+
createDocumentUsingParser
|
334
|
+
catch e
|
335
|
+
testDoc = createDocumentUsingDOM '<html><body><p>test'
|
336
|
+
createDocumentUsingDOM
|
337
|
+
finally
|
338
|
+
unless testDoc?.body?.childNodes.length is 1
|
339
|
+
return createDocumentUsingWrite
|
340
|
+
|
341
|
+
if browserSupportsTurbolinks
|
342
|
+
@visit = fetch
|
343
|
+
@rememberCurrentUrl()
|
344
|
+
@rememberCurrentState()
|
345
|
+
createDocument = browserCompatibleDocumentParser()
|
346
|
+
|
347
|
+
document.addEventListener 'click', Click.installHandlerLast, true
|
348
|
+
|
349
|
+
bypassOnLoadPopstate ->
|
350
|
+
window.addEventListener 'popstate', installHistoryChangeHandler, false
|
351
|
+
|
352
|
+
else
|
353
|
+
@visit = (url) -> document.location.href = url
|
data/lib/turbograft/version.rb
CHANGED