turbograft 0.4.7 → 0.5.0
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/README.md +5 -4
- data/lib/assets/javascripts/turbograft/click.js +44 -0
- data/lib/assets/javascripts/turbograft/component_url.js +53 -0
- data/lib/assets/javascripts/turbograft/csrf_token.js +23 -0
- data/lib/assets/javascripts/turbograft/document.js +20 -0
- data/lib/assets/javascripts/turbograft/initializers.js +83 -0
- data/lib/assets/javascripts/turbograft/link.js +58 -0
- data/lib/assets/javascripts/turbograft/page.js +105 -0
- data/lib/assets/javascripts/turbograft/remote.js +248 -0
- data/lib/assets/javascripts/turbograft/response.js +48 -0
- data/lib/assets/javascripts/turbograft/turbohead.js +159 -0
- data/lib/assets/javascripts/turbograft/turbolinks.js +504 -0
- data/lib/assets/javascripts/turbograft.js +37 -0
- data/lib/turbograft/version.rb +1 -1
- data/lib/turbograft/x_domain_blocker.rb +2 -2
- metadata +28 -28
- data/lib/assets/javascripts/turbograft/click.coffee +0 -34
- data/lib/assets/javascripts/turbograft/component_url.coffee +0 -24
- data/lib/assets/javascripts/turbograft/csrf_token.coffee +0 -9
- data/lib/assets/javascripts/turbograft/document.coffee +0 -11
- data/lib/assets/javascripts/turbograft/initializers.coffee +0 -67
- data/lib/assets/javascripts/turbograft/link.coffee +0 -41
- data/lib/assets/javascripts/turbograft/page.coffee +0 -77
- data/lib/assets/javascripts/turbograft/remote.coffee +0 -179
- data/lib/assets/javascripts/turbograft/response.coffee +0 -31
- data/lib/assets/javascripts/turbograft/turbohead.coffee +0 -142
- data/lib/assets/javascripts/turbograft/turbolinks.coffee +0 -361
- data/lib/assets/javascripts/turbograft.coffee +0 -30
@@ -1,31 +0,0 @@
|
|
1
|
-
class TurboGraft.Response
|
2
|
-
constructor: (@xhr, intendedURL) ->
|
3
|
-
if intendedURL && intendedURL.withoutHash() != @xhr.responseURL
|
4
|
-
redirectedTo = @xhr.responseURL
|
5
|
-
else
|
6
|
-
redirectedTo = @xhr.getResponseHeader('X-XHR-Redirected-To')
|
7
|
-
|
8
|
-
@finalURL = redirectedTo || intendedURL
|
9
|
-
|
10
|
-
valid: -> @hasRenderableHttpStatus() && @hasValidContent()
|
11
|
-
|
12
|
-
document: ->
|
13
|
-
if @valid()
|
14
|
-
TurboGraft.Document.create(@xhr.responseText)
|
15
|
-
|
16
|
-
hasRenderableHttpStatus: ->
|
17
|
-
return true if @xhr.status == 422 # we want to render form validations
|
18
|
-
!(400 <= @xhr.status < 600)
|
19
|
-
|
20
|
-
hasValidContent: ->
|
21
|
-
if contentType = @xhr.getResponseHeader('Content-Type')
|
22
|
-
contentType.match(/^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/)
|
23
|
-
else
|
24
|
-
throw new Error("Error encountered for XHR Response: #{this}")
|
25
|
-
|
26
|
-
toString: () ->
|
27
|
-
"URL: #{@xhr.responseURL}, " +
|
28
|
-
"ReadyState: #{@xhr.readyState}, " +
|
29
|
-
"Headers: #{@xhr.getAllResponseHeaders()}"
|
30
|
-
|
31
|
-
TurboGraft.location = () -> location.href
|
@@ -1,142 +0,0 @@
|
|
1
|
-
TRACKED_ASSET_SELECTOR = '[data-turbolinks-track]'
|
2
|
-
TRACKED_ATTRIBUTE_NAME = 'turbolinksTrack'
|
3
|
-
ANONYMOUS_TRACK_VALUE = 'true'
|
4
|
-
|
5
|
-
scriptPromises = {}
|
6
|
-
resolvePreviousRequest = null
|
7
|
-
|
8
|
-
waitForCompleteDownloads = ->
|
9
|
-
loadingPromises = Object.keys(scriptPromises).map (url) ->
|
10
|
-
scriptPromises[url]
|
11
|
-
Promise.all(loadingPromises)
|
12
|
-
|
13
|
-
class TurboGraft.TurboHead
|
14
|
-
constructor: (@activeDocument, @upstreamDocument) ->
|
15
|
-
@activeAssets = extractTrackedAssets(@activeDocument)
|
16
|
-
@upstreamAssets = extractTrackedAssets(@upstreamDocument)
|
17
|
-
@newScripts = @upstreamAssets
|
18
|
-
.filter(attributeMatches('nodeName', 'SCRIPT'))
|
19
|
-
.filter(noAttributeMatchesIn('src', @activeAssets))
|
20
|
-
|
21
|
-
@newLinks = @upstreamAssets
|
22
|
-
.filter(attributeMatches('nodeName', 'LINK'))
|
23
|
-
.filter(noAttributeMatchesIn('href', @activeAssets))
|
24
|
-
|
25
|
-
@_testAPI: {
|
26
|
-
reset: ->
|
27
|
-
scriptPromises = {}
|
28
|
-
resolvePreviousRequest = null
|
29
|
-
}
|
30
|
-
|
31
|
-
hasChangedAnonymousAssets: () ->
|
32
|
-
anonymousUpstreamAssets = @upstreamAssets
|
33
|
-
.filter(datasetMatches(TRACKED_ATTRIBUTE_NAME, ANONYMOUS_TRACK_VALUE))
|
34
|
-
anonymousActiveAssets = @activeAssets
|
35
|
-
.filter(datasetMatches(TRACKED_ATTRIBUTE_NAME, ANONYMOUS_TRACK_VALUE))
|
36
|
-
|
37
|
-
if anonymousActiveAssets.length != anonymousUpstreamAssets.length
|
38
|
-
return true
|
39
|
-
|
40
|
-
noMatchingSrc = noAttributeMatchesIn('src', anonymousUpstreamAssets)
|
41
|
-
noMatchingHref = noAttributeMatchesIn('href', anonymousUpstreamAssets)
|
42
|
-
|
43
|
-
anonymousActiveAssets.some((node) ->
|
44
|
-
noMatchingSrc(node) || noMatchingHref(node)
|
45
|
-
)
|
46
|
-
|
47
|
-
movingFromTrackedToUntracked: () ->
|
48
|
-
@upstreamAssets.length == 0 && @activeAssets.length > 0
|
49
|
-
|
50
|
-
hasNamedAssetConflicts: () ->
|
51
|
-
@newScripts
|
52
|
-
.concat(@newLinks)
|
53
|
-
.filter(noDatasetMatches(TRACKED_ATTRIBUTE_NAME, ANONYMOUS_TRACK_VALUE))
|
54
|
-
.some(datasetMatchesIn(TRACKED_ATTRIBUTE_NAME, @activeAssets))
|
55
|
-
|
56
|
-
hasAssetConflicts: () ->
|
57
|
-
@movingFromTrackedToUntracked() ||
|
58
|
-
@hasNamedAssetConflicts() ||
|
59
|
-
@hasChangedAnonymousAssets()
|
60
|
-
|
61
|
-
waitForAssets: () ->
|
62
|
-
resolvePreviousRequest?(isCanceled: true)
|
63
|
-
|
64
|
-
new Promise((resolve) =>
|
65
|
-
resolvePreviousRequest = resolve
|
66
|
-
waitForCompleteDownloads()
|
67
|
-
.then(@_insertNewAssets)
|
68
|
-
.then(waitForCompleteDownloads)
|
69
|
-
.then(resolve)
|
70
|
-
)
|
71
|
-
|
72
|
-
_insertNewAssets: () =>
|
73
|
-
updateLinkTags(@activeDocument, @newLinks)
|
74
|
-
updateScriptTags(@activeDocument, @newScripts)
|
75
|
-
|
76
|
-
extractTrackedAssets = (doc) ->
|
77
|
-
[].slice.call(doc.querySelectorAll(TRACKED_ASSET_SELECTOR))
|
78
|
-
|
79
|
-
attributeMatches = (attribute, value) ->
|
80
|
-
(node) -> node[attribute] == value
|
81
|
-
|
82
|
-
attributeMatchesIn = (attribute, collection) ->
|
83
|
-
(node) ->
|
84
|
-
collection.some((nodeFromCollection) -> node[attribute] == nodeFromCollection[attribute])
|
85
|
-
|
86
|
-
noAttributeMatchesIn = (attribute, collection) ->
|
87
|
-
(node) ->
|
88
|
-
!collection.some((nodeFromCollection) -> node[attribute] == nodeFromCollection[attribute])
|
89
|
-
|
90
|
-
datasetMatches = (attribute, value) ->
|
91
|
-
(node) -> node.dataset[attribute] == value
|
92
|
-
|
93
|
-
noDatasetMatches = (attribute, value) ->
|
94
|
-
(node) -> node.dataset[attribute] != value
|
95
|
-
|
96
|
-
datasetMatchesIn = (attribute, collection) ->
|
97
|
-
(node) ->
|
98
|
-
value = node.dataset[attribute]
|
99
|
-
collection.some(datasetMatches(attribute, value))
|
100
|
-
|
101
|
-
noDatasetMatchesIn = (attribute, collection) ->
|
102
|
-
(node) ->
|
103
|
-
value = node.dataset[attribute]
|
104
|
-
!collection.some(datasetMatches(attribute, value))
|
105
|
-
|
106
|
-
updateLinkTags = (activeDocument, newLinks) ->
|
107
|
-
# style tag load events don't work in all browsers
|
108
|
-
# as such we just hope they load ¯\_(ツ)_/¯
|
109
|
-
newLinks.forEach((linkNode) ->
|
110
|
-
newNode = linkNode.cloneNode()
|
111
|
-
activeDocument.head.appendChild(newNode)
|
112
|
-
triggerEvent("page:after-link-inserted", newNode)
|
113
|
-
)
|
114
|
-
|
115
|
-
updateScriptTags = (activeDocument, newScripts) ->
|
116
|
-
promise = Promise.resolve()
|
117
|
-
newScripts.forEach (scriptNode) ->
|
118
|
-
promise = promise.then(-> insertScript(activeDocument, scriptNode))
|
119
|
-
promise
|
120
|
-
|
121
|
-
insertScript = (activeDocument, scriptNode) ->
|
122
|
-
url = scriptNode.src
|
123
|
-
if scriptPromises[url]
|
124
|
-
return scriptPromises[url]
|
125
|
-
|
126
|
-
# Clone script tags to guarantee browser execution.
|
127
|
-
newNode = activeDocument.createElement('SCRIPT')
|
128
|
-
newNode.setAttribute(attr.name, attr.value) for attr in scriptNode.attributes
|
129
|
-
newNode.appendChild(activeDocument.createTextNode(scriptNode.innerHTML))
|
130
|
-
|
131
|
-
scriptPromises[url] = new Promise((resolve) ->
|
132
|
-
onAssetEvent = (event) ->
|
133
|
-
triggerEvent("page:#script-error", event) if event.type == 'error'
|
134
|
-
newNode.removeEventListener('load', onAssetEvent)
|
135
|
-
newNode.removeEventListener('error', onAssetEvent)
|
136
|
-
resolve()
|
137
|
-
|
138
|
-
newNode.addEventListener('load', onAssetEvent)
|
139
|
-
newNode.addEventListener('error', onAssetEvent)
|
140
|
-
activeDocument.head.appendChild(newNode)
|
141
|
-
triggerEvent("page:after-script-inserted", newNode)
|
142
|
-
)
|
@@ -1,361 +0,0 @@
|
|
1
|
-
Response = TurboGraft.Response
|
2
|
-
TurboHead = TurboGraft.TurboHead
|
3
|
-
jQuery = window.jQuery
|
4
|
-
|
5
|
-
xhr = null
|
6
|
-
activeDocument = document
|
7
|
-
|
8
|
-
installDocumentReadyPageEventTriggers = ->
|
9
|
-
activeDocument.addEventListener 'DOMContentLoaded', ( ->
|
10
|
-
triggerEvent 'page:change'
|
11
|
-
triggerEvent 'page:update'
|
12
|
-
), true
|
13
|
-
|
14
|
-
installJqueryAjaxSuccessPageUpdateTrigger = ->
|
15
|
-
if typeof jQuery isnt 'undefined'
|
16
|
-
jQuery(activeDocument).on 'ajaxSuccess', (event, xhr, settings) ->
|
17
|
-
return unless jQuery.trim xhr.responseText
|
18
|
-
triggerEvent 'page:update'
|
19
|
-
|
20
|
-
# Handle bug in Firefox 26/27 where history.state is initially undefined
|
21
|
-
historyStateIsDefined =
|
22
|
-
window.history.state != undefined or navigator.userAgent.match /Firefox\/2[6|7]/
|
23
|
-
|
24
|
-
browserSupportsPushState =
|
25
|
-
window.history and window.history.pushState and window.history.replaceState and historyStateIsDefined
|
26
|
-
|
27
|
-
window.triggerEvent = (name, data) ->
|
28
|
-
event = activeDocument.createEvent 'Events'
|
29
|
-
event.data = data if data
|
30
|
-
event.initEvent name, true, true
|
31
|
-
activeDocument.dispatchEvent event
|
32
|
-
|
33
|
-
window.triggerEventFor = (name, node, data) ->
|
34
|
-
event = activeDocument.createEvent 'Events'
|
35
|
-
event.data = data if data
|
36
|
-
event.initEvent name, true, true
|
37
|
-
node.dispatchEvent event
|
38
|
-
|
39
|
-
popCookie = (name) ->
|
40
|
-
value = activeDocument.cookie.match(new RegExp(name+"=(\\w+)"))?[1].toUpperCase() or ''
|
41
|
-
activeDocument.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'
|
42
|
-
value
|
43
|
-
|
44
|
-
requestMethodIsSafe =
|
45
|
-
popCookie('request_method') in ['GET','']
|
46
|
-
|
47
|
-
browserSupportsTurbolinks = browserSupportsPushState and requestMethodIsSafe
|
48
|
-
|
49
|
-
browserSupportsCustomEvents =
|
50
|
-
activeDocument.addEventListener and activeDocument.createEvent
|
51
|
-
|
52
|
-
if browserSupportsCustomEvents
|
53
|
-
installDocumentReadyPageEventTriggers()
|
54
|
-
installJqueryAjaxSuccessPageUpdateTrigger()
|
55
|
-
|
56
|
-
replaceNode = (newNode, oldNode) ->
|
57
|
-
replacedNode = oldNode.parentNode.replaceChild(newNode, oldNode)
|
58
|
-
triggerEvent('page:after-node-removed', replacedNode)
|
59
|
-
|
60
|
-
removeNode = (node) ->
|
61
|
-
removedNode = node.parentNode.removeChild(node)
|
62
|
-
triggerEvent('page:after-node-removed', removedNode)
|
63
|
-
|
64
|
-
# TODO: triggerEvent should be accessible to all these guys
|
65
|
-
# on some kind of eventbus
|
66
|
-
# TODO: clean up everything above me ^
|
67
|
-
# TODO: decide on the public API
|
68
|
-
class window.Turbolinks
|
69
|
-
currentState = null
|
70
|
-
referer = null
|
71
|
-
|
72
|
-
fetch = (url, options = {}) ->
|
73
|
-
return if pageChangePrevented(url)
|
74
|
-
url = new ComponentUrl(url)
|
75
|
-
|
76
|
-
rememberReferer()
|
77
|
-
|
78
|
-
fetchReplacement(url, options)
|
79
|
-
|
80
|
-
isPartialReplace = (response, options) ->
|
81
|
-
Boolean(
|
82
|
-
options.partialReplace ||
|
83
|
-
options.onlyKeys?.length ||
|
84
|
-
options.exceptKeys?.length
|
85
|
-
)
|
86
|
-
|
87
|
-
@fullPageNavigate: (url) ->
|
88
|
-
if url?
|
89
|
-
url = (new ComponentUrl(url)).absolute
|
90
|
-
triggerEvent('page:before-full-refresh', url: url)
|
91
|
-
activeDocument.location.href = url
|
92
|
-
return
|
93
|
-
|
94
|
-
@pushState: (state, title, url) ->
|
95
|
-
window.history.pushState(state, title, url)
|
96
|
-
|
97
|
-
@replaceState: (state, title, url) ->
|
98
|
-
window.history.replaceState(state, title, url)
|
99
|
-
|
100
|
-
@document: (documentToUse) ->
|
101
|
-
activeDocument = documentToUse if documentToUse
|
102
|
-
activeDocument
|
103
|
-
|
104
|
-
fetchReplacement = (url, options) ->
|
105
|
-
triggerEvent 'page:fetch', url: url.absolute
|
106
|
-
|
107
|
-
if xhr?
|
108
|
-
# Workaround for sinon xhr.abort()
|
109
|
-
# https://github.com/sinonjs/sinon/issues/432#issuecomment-216917023
|
110
|
-
xhr.readyState = 0
|
111
|
-
xhr.statusText = "abort"
|
112
|
-
xhr.abort()
|
113
|
-
|
114
|
-
xhr = new XMLHttpRequest
|
115
|
-
|
116
|
-
xhr.open 'GET', url.withoutHashForIE10compatibility(), true
|
117
|
-
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
118
|
-
xhr.setRequestHeader 'X-XHR-Referer', referer
|
119
|
-
options.headers ?= {}
|
120
|
-
|
121
|
-
for k,v of options.headers
|
122
|
-
xhr.setRequestHeader k, v
|
123
|
-
|
124
|
-
xhr.onload = ->
|
125
|
-
if xhr.status >= 500
|
126
|
-
Turbolinks.fullPageNavigate(url)
|
127
|
-
else
|
128
|
-
Turbolinks.loadPage(url, xhr, options)
|
129
|
-
xhr = null
|
130
|
-
|
131
|
-
xhr.onerror = ->
|
132
|
-
# Workaround for sinon xhr.abort()
|
133
|
-
if xhr.statusText == "abort"
|
134
|
-
xhr = null
|
135
|
-
return
|
136
|
-
Turbolinks.fullPageNavigate(url)
|
137
|
-
|
138
|
-
xhr.send()
|
139
|
-
|
140
|
-
return
|
141
|
-
|
142
|
-
@loadPage: (url, xhr, options = {}) ->
|
143
|
-
triggerEvent 'page:receive'
|
144
|
-
response = new Response(xhr, url)
|
145
|
-
options.updatePushState ?= true
|
146
|
-
options.partialReplace = isPartialReplace(response, options)
|
147
|
-
|
148
|
-
unless upstreamDocument = response.document()
|
149
|
-
triggerEvent 'page:error', xhr
|
150
|
-
Turbolinks.fullPageNavigate(response.finalURL)
|
151
|
-
return
|
152
|
-
|
153
|
-
if options.partialReplace
|
154
|
-
updateBody(upstreamDocument, response, options)
|
155
|
-
return
|
156
|
-
|
157
|
-
turbohead = new TurboHead(activeDocument, upstreamDocument)
|
158
|
-
if turbohead.hasAssetConflicts()
|
159
|
-
return Turbolinks.fullPageNavigate(response.finalURL)
|
160
|
-
|
161
|
-
turbohead.waitForAssets().then((result) ->
|
162
|
-
updateBody(upstreamDocument, response, options) unless result?.isCanceled
|
163
|
-
)
|
164
|
-
|
165
|
-
updateBody = (upstreamDocument, response, options) ->
|
166
|
-
nodes = changePage(
|
167
|
-
upstreamDocument.querySelector('title')?.textContent,
|
168
|
-
removeNoscriptTags(upstreamDocument.querySelector('body')),
|
169
|
-
CSRFToken.get(upstreamDocument).token,
|
170
|
-
'runScripts',
|
171
|
-
options
|
172
|
-
)
|
173
|
-
reflectNewUrl(response.finalURL) if options.updatePushState
|
174
|
-
|
175
|
-
Turbolinks.resetScrollPosition() unless options.partialReplace
|
176
|
-
|
177
|
-
options.callback?()
|
178
|
-
triggerEvent 'page:load', nodes
|
179
|
-
|
180
|
-
changePage = (title, body, csrfToken, runScripts, options = {}) ->
|
181
|
-
activeDocument.title = title if title
|
182
|
-
|
183
|
-
if options.onlyKeys?.length
|
184
|
-
nodesToRefresh = [].concat(getNodesWithRefreshAlways(), getNodesMatchingRefreshKeys(options.onlyKeys))
|
185
|
-
nodes = refreshNodes(nodesToRefresh, body)
|
186
|
-
setAutofocusElement() if anyAutofocusElement(nodes)
|
187
|
-
return nodes
|
188
|
-
else
|
189
|
-
refreshNodes(getNodesWithRefreshAlways(), body)
|
190
|
-
persistStaticElements(body)
|
191
|
-
if options.exceptKeys?.length
|
192
|
-
refreshAllExceptWithKeys(options.exceptKeys, body)
|
193
|
-
else
|
194
|
-
deleteRefreshNeverNodes(body)
|
195
|
-
|
196
|
-
triggerEvent 'page:before-replace'
|
197
|
-
replaceNode(body, activeDocument.body)
|
198
|
-
CSRFToken.update csrfToken if csrfToken?
|
199
|
-
setAutofocusElement()
|
200
|
-
executeScriptTags() if runScripts
|
201
|
-
currentState = window.history.state
|
202
|
-
triggerEvent 'page:change'
|
203
|
-
triggerEvent 'page:update'
|
204
|
-
|
205
|
-
return
|
206
|
-
|
207
|
-
getNodesMatchingRefreshKeys = (keys) ->
|
208
|
-
matchingNodes = []
|
209
|
-
for key in keys
|
210
|
-
for node in TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh', key)
|
211
|
-
matchingNodes.push(node)
|
212
|
-
|
213
|
-
return matchingNodes
|
214
|
-
|
215
|
-
getNodesWithRefreshAlways = ->
|
216
|
-
matchingNodes = []
|
217
|
-
for node in TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh-always')
|
218
|
-
matchingNodes.push(node)
|
219
|
-
|
220
|
-
return matchingNodes
|
221
|
-
|
222
|
-
anyAutofocusElement = (nodes) ->
|
223
|
-
for node in nodes
|
224
|
-
if node.querySelectorAll('input[autofocus], textarea[autofocus]').length > 0
|
225
|
-
return true
|
226
|
-
|
227
|
-
false
|
228
|
-
|
229
|
-
setAutofocusElement = ->
|
230
|
-
autofocusElement = (list = activeDocument.querySelectorAll 'input[autofocus], textarea[autofocus]')[list.length - 1]
|
231
|
-
if autofocusElement and activeDocument.activeElement isnt autofocusElement
|
232
|
-
autofocusElement.focus()
|
233
|
-
|
234
|
-
deleteRefreshNeverNodes = (body) ->
|
235
|
-
for node in TurboGraft.querySelectorAllTGAttribute(body, 'refresh-never')
|
236
|
-
removeNode(node)
|
237
|
-
|
238
|
-
return
|
239
|
-
|
240
|
-
refreshNodes = (allNodesToBeRefreshed, body) ->
|
241
|
-
triggerEvent 'page:before-partial-replace', allNodesToBeRefreshed
|
242
|
-
|
243
|
-
parentIsRefreshing = (node) ->
|
244
|
-
for potentialParent in allNodesToBeRefreshed when node != potentialParent
|
245
|
-
return true if potentialParent.contains(node)
|
246
|
-
false
|
247
|
-
|
248
|
-
refreshedNodes = []
|
249
|
-
for existingNode in allNodesToBeRefreshed
|
250
|
-
continue if parentIsRefreshing(existingNode)
|
251
|
-
|
252
|
-
unless nodeId = existingNode.getAttribute('id')
|
253
|
-
throw new Error "Turbolinks refresh: Refresh key elements must have an id."
|
254
|
-
|
255
|
-
if newNode = body.querySelector("##{ nodeId }")
|
256
|
-
newNode = newNode.cloneNode(true)
|
257
|
-
replaceNode(newNode, existingNode)
|
258
|
-
|
259
|
-
if newNode.nodeName == 'SCRIPT' && newNode.dataset.turbolinksEval != "false"
|
260
|
-
executeScriptTag(newNode)
|
261
|
-
else
|
262
|
-
refreshedNodes.push(newNode)
|
263
|
-
|
264
|
-
else if !TurboGraft.hasTGAttribute(existingNode, "refresh-always")
|
265
|
-
removeNode(existingNode)
|
266
|
-
|
267
|
-
refreshedNodes
|
268
|
-
|
269
|
-
keepNodes = (body, allNodesToKeep) ->
|
270
|
-
for existingNode in allNodesToKeep
|
271
|
-
unless nodeId = existingNode.getAttribute('id')
|
272
|
-
throw new Error("TurboGraft refresh: Kept nodes must have an id.")
|
273
|
-
|
274
|
-
if remoteNode = body.querySelector("##{ nodeId }")
|
275
|
-
replaceNode(existingNode, remoteNode)
|
276
|
-
|
277
|
-
persistStaticElements = (body) ->
|
278
|
-
allNodesToKeep = []
|
279
|
-
|
280
|
-
nodes = TurboGraft.querySelectorAllTGAttribute(activeDocument, 'tg-static')
|
281
|
-
allNodesToKeep.push(node) for node in nodes
|
282
|
-
|
283
|
-
keepNodes(body, allNodesToKeep)
|
284
|
-
return
|
285
|
-
|
286
|
-
refreshAllExceptWithKeys = (keys, body) ->
|
287
|
-
allNodesToKeep = []
|
288
|
-
|
289
|
-
for key in keys
|
290
|
-
for node in TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh', key)
|
291
|
-
allNodesToKeep.push(node)
|
292
|
-
|
293
|
-
keepNodes(body, allNodesToKeep)
|
294
|
-
return
|
295
|
-
|
296
|
-
executeScriptTags = ->
|
297
|
-
scripts = Array::slice.call activeDocument.body.querySelectorAll 'script:not([data-turbolinks-eval="false"])'
|
298
|
-
for script in scripts when script.type in ['', 'text/javascript']
|
299
|
-
executeScriptTag(script)
|
300
|
-
return
|
301
|
-
|
302
|
-
executeScriptTag = (script) ->
|
303
|
-
copy = activeDocument.createElement 'script'
|
304
|
-
copy.setAttribute attr.name, attr.value for attr in script.attributes
|
305
|
-
copy.appendChild activeDocument.createTextNode script.innerHTML
|
306
|
-
{ parentNode, nextSibling } = script
|
307
|
-
parentNode.removeChild script
|
308
|
-
parentNode.insertBefore copy, nextSibling
|
309
|
-
return
|
310
|
-
|
311
|
-
removeNoscriptTags = (node) ->
|
312
|
-
node.innerHTML = node.innerHTML.replace /<noscript[\S\s]*?<\/noscript>/ig, ''
|
313
|
-
node
|
314
|
-
|
315
|
-
reflectNewUrl = (url) ->
|
316
|
-
if (url = new ComponentUrl url).absolute isnt referer
|
317
|
-
Turbolinks.pushState { turbolinks: true, url: url.absolute }, '', url.absolute
|
318
|
-
return
|
319
|
-
|
320
|
-
rememberReferer = ->
|
321
|
-
referer = activeDocument.location.href
|
322
|
-
|
323
|
-
@rememberCurrentUrl: ->
|
324
|
-
Turbolinks.replaceState { turbolinks: true, url: activeDocument.location.href }, '', activeDocument.location.href
|
325
|
-
|
326
|
-
@rememberCurrentState: ->
|
327
|
-
currentState = window.history.state
|
328
|
-
|
329
|
-
recallScrollPosition = (page) ->
|
330
|
-
window.scrollTo page.positionX, page.positionY
|
331
|
-
|
332
|
-
@resetScrollPosition: ->
|
333
|
-
if activeDocument.location.hash
|
334
|
-
activeDocument.location.href = activeDocument.location.href
|
335
|
-
else
|
336
|
-
window.scrollTo 0, 0
|
337
|
-
|
338
|
-
pageChangePrevented = (url) ->
|
339
|
-
!triggerEvent('page:before-change', url)
|
340
|
-
|
341
|
-
installHistoryChangeHandler = (event) ->
|
342
|
-
if event.state?.turbolinks
|
343
|
-
Turbolinks.visit event.target.location.href
|
344
|
-
|
345
|
-
# Delay execution of function long enough to miss the popstate event
|
346
|
-
# some browsers fire on the initial page load.
|
347
|
-
bypassOnLoadPopstate = (fn) ->
|
348
|
-
setTimeout fn, 500
|
349
|
-
|
350
|
-
if browserSupportsTurbolinks
|
351
|
-
@visit = fetch
|
352
|
-
@rememberCurrentUrl()
|
353
|
-
@rememberCurrentState()
|
354
|
-
|
355
|
-
activeDocument.addEventListener 'click', Click.installHandlerLast, true
|
356
|
-
|
357
|
-
bypassOnLoadPopstate ->
|
358
|
-
window.addEventListener 'popstate', installHistoryChangeHandler, false
|
359
|
-
|
360
|
-
else
|
361
|
-
@visit = (url) -> activeDocument.location.href = url
|
@@ -1,30 +0,0 @@
|
|
1
|
-
#= require_self
|
2
|
-
#= require_tree ./turbograft
|
3
|
-
|
4
|
-
window.TurboGraft ?= { handlers: {} }
|
5
|
-
|
6
|
-
TurboGraft.tgAttribute = (attr) ->
|
7
|
-
tgAttr = if attr[0...3] == 'tg-'
|
8
|
-
"data-#{attr}"
|
9
|
-
else
|
10
|
-
"data-tg-#{attr}"
|
11
|
-
|
12
|
-
TurboGraft.getTGAttribute = (node, attr) ->
|
13
|
-
tgAttr = TurboGraft.tgAttribute(attr)
|
14
|
-
node.getAttribute(tgAttr) || node.getAttribute(attr)
|
15
|
-
|
16
|
-
TurboGraft.removeTGAttribute = (node, attr) ->
|
17
|
-
tgAttr = TurboGraft.tgAttribute(attr)
|
18
|
-
node.removeAttribute(tgAttr)
|
19
|
-
node.removeAttribute(attr)
|
20
|
-
|
21
|
-
TurboGraft.hasTGAttribute = (node, attr) ->
|
22
|
-
tgAttr = TurboGraft.tgAttribute(attr)
|
23
|
-
node.hasAttribute(tgAttr) || node.hasAttribute(attr)
|
24
|
-
|
25
|
-
TurboGraft.querySelectorAllTGAttribute = (node, attr, value = null) ->
|
26
|
-
tgAttr = TurboGraft.tgAttribute(attr)
|
27
|
-
if value
|
28
|
-
node.querySelectorAll("[#{tgAttr}=#{value}], [#{attr}=#{value}]")
|
29
|
-
else
|
30
|
-
node.querySelectorAll("[#{tgAttr}], [#{attr}]")
|