turbograft 0.4.8 → 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
- metadata +27 -27
- 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
@@ -0,0 +1,48 @@
|
|
1
|
+
/*
|
2
|
+
* decaffeinate suggestions:
|
3
|
+
* DS102: Remove unnecessary code created because of implicit returns
|
4
|
+
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
5
|
+
*/
|
6
|
+
TurboGraft.Response = class Response {
|
7
|
+
constructor(xhr, intendedURL) {
|
8
|
+
let redirectedTo;
|
9
|
+
this.xhr = xhr;
|
10
|
+
if (intendedURL && (intendedURL.withoutHash() !== this.xhr.responseURL)) {
|
11
|
+
redirectedTo = this.xhr.responseURL;
|
12
|
+
} else {
|
13
|
+
redirectedTo = this.xhr.getResponseHeader('X-XHR-Redirected-To');
|
14
|
+
}
|
15
|
+
|
16
|
+
this.finalURL = redirectedTo || intendedURL;
|
17
|
+
}
|
18
|
+
|
19
|
+
valid() { return this.hasRenderableHttpStatus() && this.hasValidContent(); }
|
20
|
+
|
21
|
+
document() {
|
22
|
+
if (this.valid()) {
|
23
|
+
return TurboGraft.Document.create(this.xhr.responseText);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
hasRenderableHttpStatus() {
|
28
|
+
if (this.xhr.status === 422) { return true; } // we want to render form validations
|
29
|
+
return !(400 <= this.xhr.status && this.xhr.status < 600);
|
30
|
+
}
|
31
|
+
|
32
|
+
hasValidContent() {
|
33
|
+
let contentType;
|
34
|
+
if (contentType = this.xhr.getResponseHeader('Content-Type')) {
|
35
|
+
return contentType.match(/^(?:text\/html|application\/xhtml\+xml|application\/xml)(?:;|$)/);
|
36
|
+
} else {
|
37
|
+
throw new Error(`Error encountered for XHR Response: ${this}`);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
toString() {
|
42
|
+
return `URL: ${this.xhr.responseURL}, ` +
|
43
|
+
`ReadyState: ${this.xhr.readyState}, ` +
|
44
|
+
`Headers: ${this.xhr.getAllResponseHeaders()}`;
|
45
|
+
}
|
46
|
+
};
|
47
|
+
|
48
|
+
TurboGraft.location = () => location.href;
|
@@ -0,0 +1,159 @@
|
|
1
|
+
/*
|
2
|
+
* decaffeinate suggestions:
|
3
|
+
* DS101: Remove unnecessary use of Array.from
|
4
|
+
* DS102: Remove unnecessary code created because of implicit returns
|
5
|
+
* DS206: Consider reworking classes to avoid initClass
|
6
|
+
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
|
7
|
+
*/
|
8
|
+
const TRACKED_ASSET_SELECTOR = '[data-turbolinks-track]';
|
9
|
+
const TRACKED_ATTRIBUTE_NAME = 'turbolinksTrack';
|
10
|
+
const ANONYMOUS_TRACK_VALUE = 'true';
|
11
|
+
|
12
|
+
let scriptPromises = {};
|
13
|
+
let resolvePreviousRequest = null;
|
14
|
+
|
15
|
+
const waitForCompleteDownloads = function() {
|
16
|
+
const loadingPromises = Object.keys(scriptPromises).map(url => scriptPromises[url]);
|
17
|
+
return Promise.all(loadingPromises);
|
18
|
+
};
|
19
|
+
|
20
|
+
const Cls = (TurboGraft.TurboHead = class TurboHead {
|
21
|
+
static initClass() {
|
22
|
+
|
23
|
+
this._testAPI = {
|
24
|
+
reset() {
|
25
|
+
scriptPromises = {};
|
26
|
+
return resolvePreviousRequest = null;
|
27
|
+
}
|
28
|
+
};
|
29
|
+
}
|
30
|
+
constructor(activeDocument, upstreamDocument) {
|
31
|
+
this._insertNewAssets = this._insertNewAssets.bind(this);
|
32
|
+
this.activeDocument = activeDocument;
|
33
|
+
this.upstreamDocument = upstreamDocument;
|
34
|
+
this.activeAssets = extractTrackedAssets(this.activeDocument);
|
35
|
+
this.upstreamAssets = extractTrackedAssets(this.upstreamDocument);
|
36
|
+
this.newScripts = this.upstreamAssets
|
37
|
+
.filter(attributeMatches('nodeName', 'SCRIPT'))
|
38
|
+
.filter(noAttributeMatchesIn('src', this.activeAssets));
|
39
|
+
|
40
|
+
this.newLinks = this.upstreamAssets
|
41
|
+
.filter(attributeMatches('nodeName', 'LINK'))
|
42
|
+
.filter(noAttributeMatchesIn('href', this.activeAssets));
|
43
|
+
}
|
44
|
+
|
45
|
+
hasChangedAnonymousAssets() {
|
46
|
+
const anonymousUpstreamAssets = this.upstreamAssets
|
47
|
+
.filter(datasetMatches(TRACKED_ATTRIBUTE_NAME, ANONYMOUS_TRACK_VALUE));
|
48
|
+
const anonymousActiveAssets = this.activeAssets
|
49
|
+
.filter(datasetMatches(TRACKED_ATTRIBUTE_NAME, ANONYMOUS_TRACK_VALUE));
|
50
|
+
|
51
|
+
if (anonymousActiveAssets.length !== anonymousUpstreamAssets.length) {
|
52
|
+
return true;
|
53
|
+
}
|
54
|
+
|
55
|
+
const noMatchingSrc = noAttributeMatchesIn('src', anonymousUpstreamAssets);
|
56
|
+
const noMatchingHref = noAttributeMatchesIn('href', anonymousUpstreamAssets);
|
57
|
+
|
58
|
+
return anonymousActiveAssets.some(node => noMatchingSrc(node) || noMatchingHref(node));
|
59
|
+
}
|
60
|
+
|
61
|
+
movingFromTrackedToUntracked() {
|
62
|
+
return (this.upstreamAssets.length === 0) && (this.activeAssets.length > 0);
|
63
|
+
}
|
64
|
+
|
65
|
+
hasNamedAssetConflicts() {
|
66
|
+
return this.newScripts
|
67
|
+
.concat(this.newLinks)
|
68
|
+
.filter(noDatasetMatches(TRACKED_ATTRIBUTE_NAME, ANONYMOUS_TRACK_VALUE))
|
69
|
+
.some(datasetMatchesIn(TRACKED_ATTRIBUTE_NAME, this.activeAssets));
|
70
|
+
}
|
71
|
+
|
72
|
+
hasAssetConflicts() {
|
73
|
+
return this.movingFromTrackedToUntracked() ||
|
74
|
+
this.hasNamedAssetConflicts() ||
|
75
|
+
this.hasChangedAnonymousAssets();
|
76
|
+
}
|
77
|
+
|
78
|
+
waitForAssets() {
|
79
|
+
if (typeof resolvePreviousRequest === 'function') {
|
80
|
+
resolvePreviousRequest({isCanceled: true});
|
81
|
+
}
|
82
|
+
|
83
|
+
return new Promise(resolve => {
|
84
|
+
resolvePreviousRequest = resolve;
|
85
|
+
return waitForCompleteDownloads()
|
86
|
+
.then(this._insertNewAssets)
|
87
|
+
.then(waitForCompleteDownloads)
|
88
|
+
.then(resolve);
|
89
|
+
});
|
90
|
+
}
|
91
|
+
|
92
|
+
_insertNewAssets() {
|
93
|
+
updateLinkTags(this.activeDocument, this.newLinks);
|
94
|
+
return updateScriptTags(this.activeDocument, this.newScripts);
|
95
|
+
}
|
96
|
+
});
|
97
|
+
Cls.initClass();
|
98
|
+
|
99
|
+
var extractTrackedAssets = doc => [].slice.call(doc.querySelectorAll(TRACKED_ASSET_SELECTOR));
|
100
|
+
|
101
|
+
var attributeMatches = (attribute, value) => node => node[attribute] === value;
|
102
|
+
|
103
|
+
const attributeMatchesIn = (attribute, collection) => node => collection.some(nodeFromCollection => node[attribute] === nodeFromCollection[attribute]);
|
104
|
+
|
105
|
+
var noAttributeMatchesIn = (attribute, collection) => node => !collection.some(nodeFromCollection => node[attribute] === nodeFromCollection[attribute]);
|
106
|
+
|
107
|
+
var datasetMatches = (attribute, value) => node => node.dataset[attribute] === value;
|
108
|
+
|
109
|
+
var noDatasetMatches = (attribute, value) => node => node.dataset[attribute] !== value;
|
110
|
+
|
111
|
+
var datasetMatchesIn = (attribute, collection) => (function(node) {
|
112
|
+
const value = node.dataset[attribute];
|
113
|
+
return collection.some(datasetMatches(attribute, value));
|
114
|
+
});
|
115
|
+
|
116
|
+
const noDatasetMatchesIn = (attribute, collection) => (function(node) {
|
117
|
+
const value = node.dataset[attribute];
|
118
|
+
return !collection.some(datasetMatches(attribute, value));
|
119
|
+
});
|
120
|
+
|
121
|
+
var updateLinkTags = (activeDocument, newLinks) => // style tag load events don't work in all browsers
|
122
|
+
// as such we just hope they load ¯\_(ツ)_/¯
|
123
|
+
newLinks.forEach(function(linkNode) {
|
124
|
+
const newNode = linkNode.cloneNode();
|
125
|
+
activeDocument.head.appendChild(newNode);
|
126
|
+
return triggerEvent("page:after-link-inserted", newNode);
|
127
|
+
});
|
128
|
+
|
129
|
+
var updateScriptTags = function(activeDocument, newScripts) {
|
130
|
+
let promise = Promise.resolve();
|
131
|
+
newScripts.forEach(scriptNode => promise = promise.then(() => insertScript(activeDocument, scriptNode)));
|
132
|
+
return promise;
|
133
|
+
};
|
134
|
+
|
135
|
+
var insertScript = function(activeDocument, scriptNode) {
|
136
|
+
const url = scriptNode.src;
|
137
|
+
if (scriptPromises[url]) {
|
138
|
+
return scriptPromises[url];
|
139
|
+
}
|
140
|
+
|
141
|
+
// Clone script tags to guarantee browser execution.
|
142
|
+
const newNode = activeDocument.createElement('SCRIPT');
|
143
|
+
for (var attr of Array.from(scriptNode.attributes)) { newNode.setAttribute(attr.name, attr.value); }
|
144
|
+
newNode.appendChild(activeDocument.createTextNode(scriptNode.innerHTML));
|
145
|
+
|
146
|
+
return scriptPromises[url] = new Promise(function(resolve) {
|
147
|
+
var onAssetEvent = function(event) {
|
148
|
+
if (event.type === 'error') { triggerEvent("page:#script-error", event); }
|
149
|
+
newNode.removeEventListener('load', onAssetEvent);
|
150
|
+
newNode.removeEventListener('error', onAssetEvent);
|
151
|
+
return resolve();
|
152
|
+
};
|
153
|
+
|
154
|
+
newNode.addEventListener('load', onAssetEvent);
|
155
|
+
newNode.addEventListener('error', onAssetEvent);
|
156
|
+
activeDocument.head.appendChild(newNode);
|
157
|
+
return triggerEvent("page:after-script-inserted", newNode);
|
158
|
+
});
|
159
|
+
};
|
@@ -0,0 +1,504 @@
|
|
1
|
+
(function() {
|
2
|
+
var Response, TurboHead, activeDocument, browserSupportsCustomEvents, browserSupportsPushState, browserSupportsTurbolinks, historyStateIsDefined, installDocumentReadyPageEventTriggers, installJqueryAjaxSuccessPageUpdateTrigger, jQuery, popCookie, ref, removeNode, replaceNode, requestMethodIsSafe, xhr;
|
3
|
+
|
4
|
+
Response = TurboGraft.Response;
|
5
|
+
|
6
|
+
TurboHead = TurboGraft.TurboHead;
|
7
|
+
|
8
|
+
jQuery = window.jQuery;
|
9
|
+
|
10
|
+
xhr = null;
|
11
|
+
|
12
|
+
activeDocument = document;
|
13
|
+
|
14
|
+
installDocumentReadyPageEventTriggers = function() {
|
15
|
+
return activeDocument.addEventListener('DOMContentLoaded', (function() {
|
16
|
+
triggerEvent('page:change');
|
17
|
+
return triggerEvent('page:update');
|
18
|
+
}), true);
|
19
|
+
};
|
20
|
+
|
21
|
+
installJqueryAjaxSuccessPageUpdateTrigger = function() {
|
22
|
+
if (typeof jQuery !== 'undefined') {
|
23
|
+
return jQuery(activeDocument).on('ajaxSuccess', function(event, xhr, settings) {
|
24
|
+
if (!jQuery.trim(xhr.responseText)) {
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
return triggerEvent('page:update');
|
28
|
+
});
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
historyStateIsDefined = window.history.state !== void 0 || navigator.userAgent.match(/Firefox\/2[6|7]/);
|
33
|
+
|
34
|
+
browserSupportsPushState = window.history && window.history.pushState && window.history.replaceState && historyStateIsDefined;
|
35
|
+
|
36
|
+
window.triggerEvent = function(name, data) {
|
37
|
+
var event;
|
38
|
+
event = activeDocument.createEvent('Events');
|
39
|
+
if (data) {
|
40
|
+
event.data = data;
|
41
|
+
}
|
42
|
+
event.initEvent(name, true, true);
|
43
|
+
return activeDocument.dispatchEvent(event);
|
44
|
+
};
|
45
|
+
|
46
|
+
window.triggerEventFor = function(name, node, data) {
|
47
|
+
var event;
|
48
|
+
event = activeDocument.createEvent('Events');
|
49
|
+
if (data) {
|
50
|
+
event.data = data;
|
51
|
+
}
|
52
|
+
event.initEvent(name, true, true);
|
53
|
+
return node.dispatchEvent(event);
|
54
|
+
};
|
55
|
+
|
56
|
+
popCookie = function(name) {
|
57
|
+
var ref, value;
|
58
|
+
value = ((ref = activeDocument.cookie.match(new RegExp(name + "=(\\w+)"))) != null ? ref[1].toUpperCase() : void 0) || '';
|
59
|
+
activeDocument.cookie = name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/';
|
60
|
+
return value;
|
61
|
+
};
|
62
|
+
|
63
|
+
requestMethodIsSafe = (ref = popCookie('request_method')) === 'GET' || ref === '';
|
64
|
+
|
65
|
+
browserSupportsTurbolinks = browserSupportsPushState && requestMethodIsSafe;
|
66
|
+
|
67
|
+
browserSupportsCustomEvents = activeDocument.addEventListener && activeDocument.createEvent;
|
68
|
+
|
69
|
+
if (browserSupportsCustomEvents) {
|
70
|
+
installDocumentReadyPageEventTriggers();
|
71
|
+
installJqueryAjaxSuccessPageUpdateTrigger();
|
72
|
+
}
|
73
|
+
|
74
|
+
replaceNode = function(newNode, oldNode) {
|
75
|
+
var replacedNode;
|
76
|
+
replacedNode = oldNode.parentNode.replaceChild(newNode, oldNode);
|
77
|
+
return triggerEvent('page:after-node-removed', replacedNode);
|
78
|
+
};
|
79
|
+
|
80
|
+
removeNode = function(node) {
|
81
|
+
var removedNode;
|
82
|
+
removedNode = node.parentNode.removeChild(node);
|
83
|
+
return triggerEvent('page:after-node-removed', removedNode);
|
84
|
+
};
|
85
|
+
|
86
|
+
/* TODO: triggerEvent should be accessible to all these guys
|
87
|
+
* on some kind of eventbus
|
88
|
+
* TODO: clean up everything above me ^
|
89
|
+
* TODO: decide on the public API
|
90
|
+
*/
|
91
|
+
window.Turbolinks = (function() {
|
92
|
+
var anyAutofocusElement, bypassOnLoadPopstate, changePage, currentState, deleteRefreshNeverNodes, executeScriptTag, executeScriptTags, fetch, fetchReplacement, getNodesMatchingRefreshKeys, getNodesWithRefreshAlways, installHistoryChangeHandler, isPartialReplace, keepNodes, pageChangePrevented, persistStaticElements, recallScrollPosition, referer, reflectNewUrl, refreshAllExceptWithKeys, refreshNodes, rememberReferer, removeNoscriptTags, setAutofocusElement, updateBody;
|
93
|
+
|
94
|
+
function Turbolinks() {}
|
95
|
+
|
96
|
+
currentState = null;
|
97
|
+
|
98
|
+
referer = null;
|
99
|
+
|
100
|
+
fetch = function(url, options) {
|
101
|
+
if (options == null) {
|
102
|
+
options = {};
|
103
|
+
}
|
104
|
+
if (pageChangePrevented(url)) {
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
url = new ComponentUrl(url);
|
108
|
+
rememberReferer();
|
109
|
+
return fetchReplacement(url, options);
|
110
|
+
};
|
111
|
+
|
112
|
+
isPartialReplace = function(response, options) {
|
113
|
+
var ref1, ref2;
|
114
|
+
return Boolean(options.partialReplace || ((ref1 = options.onlyKeys) != null ? ref1.length : void 0) || ((ref2 = options.exceptKeys) != null ? ref2.length : void 0));
|
115
|
+
};
|
116
|
+
|
117
|
+
Turbolinks.fullPageNavigate = function(url) {
|
118
|
+
if (url != null) {
|
119
|
+
url = (new ComponentUrl(url)).absolute;
|
120
|
+
triggerEvent('page:before-full-refresh', {
|
121
|
+
url: url
|
122
|
+
});
|
123
|
+
activeDocument.location.href = url;
|
124
|
+
}
|
125
|
+
};
|
126
|
+
|
127
|
+
Turbolinks.pushState = function(state, title, url) {
|
128
|
+
return window.history.pushState(state, title, url);
|
129
|
+
};
|
130
|
+
|
131
|
+
Turbolinks.replaceState = function(state, title, url) {
|
132
|
+
return window.history.replaceState(state, title, url);
|
133
|
+
};
|
134
|
+
|
135
|
+
Turbolinks.document = function(documentToUse) {
|
136
|
+
if (documentToUse) {
|
137
|
+
activeDocument = documentToUse;
|
138
|
+
}
|
139
|
+
return activeDocument;
|
140
|
+
};
|
141
|
+
|
142
|
+
fetchReplacement = function(url, options) {
|
143
|
+
var k, ref1, v;
|
144
|
+
triggerEvent('page:fetch', {
|
145
|
+
url: url.absolute
|
146
|
+
});
|
147
|
+
if (xhr != null) {
|
148
|
+
// Workaround for sinon xhr.abort()
|
149
|
+
// https://github.com/sinonjs/sinon/issues/432#issuecomment-216917023
|
150
|
+
xhr.readyState = 0;
|
151
|
+
xhr.statusText = "abort";
|
152
|
+
xhr.abort();
|
153
|
+
}
|
154
|
+
xhr = new XMLHttpRequest;
|
155
|
+
xhr.open('GET', url.withoutHashForIE10compatibility(), true);
|
156
|
+
xhr.setRequestHeader('Accept', 'text/html, application/xhtml+xml, application/xml');
|
157
|
+
xhr.setRequestHeader('X-XHR-Referer', referer);
|
158
|
+
if (options.headers == null) {
|
159
|
+
options.headers = {};
|
160
|
+
}
|
161
|
+
ref1 = options.headers;
|
162
|
+
for (k in ref1) {
|
163
|
+
v = ref1[k];
|
164
|
+
xhr.setRequestHeader(k, v);
|
165
|
+
}
|
166
|
+
xhr.onload = function() {
|
167
|
+
if (xhr.status >= 500) {
|
168
|
+
Turbolinks.fullPageNavigate(url);
|
169
|
+
} else {
|
170
|
+
Turbolinks.loadPage(url, xhr, options);
|
171
|
+
}
|
172
|
+
return xhr = null;
|
173
|
+
};
|
174
|
+
xhr.onerror = function() {
|
175
|
+
// Workaround for sinon xhr.abort()
|
176
|
+
if (xhr.statusText === "abort") {
|
177
|
+
xhr = null;
|
178
|
+
return;
|
179
|
+
}
|
180
|
+
return Turbolinks.fullPageNavigate(url);
|
181
|
+
};
|
182
|
+
xhr.send();
|
183
|
+
};
|
184
|
+
|
185
|
+
Turbolinks.loadPage = function(url, xhr, options) {
|
186
|
+
var response, turbohead, upstreamDocument;
|
187
|
+
if (options == null) {
|
188
|
+
options = {};
|
189
|
+
}
|
190
|
+
triggerEvent('page:receive');
|
191
|
+
response = new Response(xhr, url);
|
192
|
+
if (options.updatePushState == null) {
|
193
|
+
options.updatePushState = true;
|
194
|
+
}
|
195
|
+
options.partialReplace = isPartialReplace(response, options);
|
196
|
+
if (!(upstreamDocument = response.document())) {
|
197
|
+
triggerEvent('page:error', xhr);
|
198
|
+
Turbolinks.fullPageNavigate(response.finalURL);
|
199
|
+
return;
|
200
|
+
}
|
201
|
+
if (options.partialReplace) {
|
202
|
+
updateBody(upstreamDocument, response, options);
|
203
|
+
return;
|
204
|
+
}
|
205
|
+
turbohead = new TurboHead(activeDocument, upstreamDocument);
|
206
|
+
if (turbohead.hasAssetConflicts()) {
|
207
|
+
return Turbolinks.fullPageNavigate(response.finalURL);
|
208
|
+
}
|
209
|
+
return turbohead.waitForAssets().then(function(result) {
|
210
|
+
if (!(result != null ? result.isCanceled : void 0)) {
|
211
|
+
return updateBody(upstreamDocument, response, options);
|
212
|
+
}
|
213
|
+
});
|
214
|
+
};
|
215
|
+
|
216
|
+
updateBody = function(upstreamDocument, response, options) {
|
217
|
+
var nodes, ref1;
|
218
|
+
nodes = changePage((ref1 = upstreamDocument.querySelector('title')) != null ? ref1.textContent : void 0, removeNoscriptTags(upstreamDocument.querySelector('body')), CSRFToken.get(upstreamDocument).token, 'runScripts', options);
|
219
|
+
if (options.updatePushState) {
|
220
|
+
reflectNewUrl(response.finalURL);
|
221
|
+
}
|
222
|
+
if (!options.partialReplace) {
|
223
|
+
Turbolinks.resetScrollPosition();
|
224
|
+
}
|
225
|
+
if (typeof options.callback === "function") {
|
226
|
+
options.callback();
|
227
|
+
}
|
228
|
+
return triggerEvent('page:load', nodes);
|
229
|
+
};
|
230
|
+
|
231
|
+
changePage = function(title, body, csrfToken, runScripts, options) {
|
232
|
+
var nodes, nodesToRefresh, ref1, ref2;
|
233
|
+
if (options == null) {
|
234
|
+
options = {};
|
235
|
+
}
|
236
|
+
if (title) {
|
237
|
+
activeDocument.title = title;
|
238
|
+
}
|
239
|
+
if ((ref1 = options.onlyKeys) != null ? ref1.length : void 0) {
|
240
|
+
nodesToRefresh = [].concat(getNodesWithRefreshAlways(), getNodesMatchingRefreshKeys(options.onlyKeys));
|
241
|
+
nodes = refreshNodes(nodesToRefresh, body);
|
242
|
+
if (anyAutofocusElement(nodes)) {
|
243
|
+
setAutofocusElement();
|
244
|
+
}
|
245
|
+
return nodes;
|
246
|
+
} else {
|
247
|
+
refreshNodes(getNodesWithRefreshAlways(), body);
|
248
|
+
persistStaticElements(body);
|
249
|
+
if ((ref2 = options.exceptKeys) != null ? ref2.length : void 0) {
|
250
|
+
refreshAllExceptWithKeys(options.exceptKeys, body);
|
251
|
+
} else {
|
252
|
+
deleteRefreshNeverNodes(body);
|
253
|
+
}
|
254
|
+
triggerEvent('page:before-replace');
|
255
|
+
replaceNode(body, activeDocument.body);
|
256
|
+
if (csrfToken != null) {
|
257
|
+
CSRFToken.update(csrfToken);
|
258
|
+
}
|
259
|
+
setAutofocusElement();
|
260
|
+
if (runScripts) {
|
261
|
+
executeScriptTags();
|
262
|
+
}
|
263
|
+
currentState = window.history.state;
|
264
|
+
triggerEvent('page:change');
|
265
|
+
triggerEvent('page:update');
|
266
|
+
}
|
267
|
+
};
|
268
|
+
|
269
|
+
getNodesMatchingRefreshKeys = function(keys) {
|
270
|
+
var i, j, key, len, len1, matchingNodes, node, ref1;
|
271
|
+
matchingNodes = [];
|
272
|
+
for (i = 0, len = keys.length; i < len; i++) {
|
273
|
+
key = keys[i];
|
274
|
+
ref1 = TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh', key);
|
275
|
+
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
276
|
+
node = ref1[j];
|
277
|
+
matchingNodes.push(node);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
return matchingNodes;
|
281
|
+
};
|
282
|
+
|
283
|
+
getNodesWithRefreshAlways = function() {
|
284
|
+
var i, len, matchingNodes, node, ref1;
|
285
|
+
matchingNodes = [];
|
286
|
+
ref1 = TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh-always');
|
287
|
+
for (i = 0, len = ref1.length; i < len; i++) {
|
288
|
+
node = ref1[i];
|
289
|
+
matchingNodes.push(node);
|
290
|
+
}
|
291
|
+
return matchingNodes;
|
292
|
+
};
|
293
|
+
|
294
|
+
anyAutofocusElement = function(nodes) {
|
295
|
+
var i, len, node;
|
296
|
+
for (i = 0, len = nodes.length; i < len; i++) {
|
297
|
+
node = nodes[i];
|
298
|
+
if (node.querySelectorAll('input[autofocus], textarea[autofocus]').length > 0) {
|
299
|
+
return true;
|
300
|
+
}
|
301
|
+
}
|
302
|
+
return false;
|
303
|
+
};
|
304
|
+
|
305
|
+
setAutofocusElement = function() {
|
306
|
+
var autofocusElement, list;
|
307
|
+
autofocusElement = (list = activeDocument.querySelectorAll('input[autofocus], textarea[autofocus]'))[list.length - 1];
|
308
|
+
if (autofocusElement && activeDocument.activeElement !== autofocusElement) {
|
309
|
+
return autofocusElement.focus();
|
310
|
+
}
|
311
|
+
};
|
312
|
+
|
313
|
+
deleteRefreshNeverNodes = function(body) {
|
314
|
+
var i, len, node, ref1;
|
315
|
+
ref1 = TurboGraft.querySelectorAllTGAttribute(body, 'refresh-never');
|
316
|
+
for (i = 0, len = ref1.length; i < len; i++) {
|
317
|
+
node = ref1[i];
|
318
|
+
removeNode(node);
|
319
|
+
}
|
320
|
+
};
|
321
|
+
|
322
|
+
refreshNodes = function(allNodesToBeRefreshed, body) {
|
323
|
+
var existingNode, i, len, newNode, nodeId, parentIsRefreshing, refreshedNodes;
|
324
|
+
triggerEvent('page:before-partial-replace', allNodesToBeRefreshed);
|
325
|
+
parentIsRefreshing = function(node) {
|
326
|
+
var i, len, potentialParent;
|
327
|
+
for (i = 0, len = allNodesToBeRefreshed.length; i < len; i++) {
|
328
|
+
potentialParent = allNodesToBeRefreshed[i];
|
329
|
+
if (node !== potentialParent) {
|
330
|
+
if (potentialParent.contains(node)) {
|
331
|
+
return true;
|
332
|
+
}
|
333
|
+
}
|
334
|
+
}
|
335
|
+
return false;
|
336
|
+
};
|
337
|
+
refreshedNodes = [];
|
338
|
+
for (i = 0, len = allNodesToBeRefreshed.length; i < len; i++) {
|
339
|
+
existingNode = allNodesToBeRefreshed[i];
|
340
|
+
if (parentIsRefreshing(existingNode)) {
|
341
|
+
continue;
|
342
|
+
}
|
343
|
+
if (!(nodeId = existingNode.getAttribute('id'))) {
|
344
|
+
throw new Error("Turbolinks refresh: Refresh key elements must have an id.");
|
345
|
+
}
|
346
|
+
if (newNode = body.querySelector("#" + nodeId)) {
|
347
|
+
newNode = newNode.cloneNode(true);
|
348
|
+
replaceNode(newNode, existingNode);
|
349
|
+
if (newNode.nodeName === 'SCRIPT' && newNode.dataset.turbolinksEval !== "false") {
|
350
|
+
executeScriptTag(newNode);
|
351
|
+
} else {
|
352
|
+
refreshedNodes.push(newNode);
|
353
|
+
}
|
354
|
+
} else if (!TurboGraft.hasTGAttribute(existingNode, "refresh-always")) {
|
355
|
+
removeNode(existingNode);
|
356
|
+
}
|
357
|
+
}
|
358
|
+
return refreshedNodes;
|
359
|
+
};
|
360
|
+
|
361
|
+
keepNodes = function(body, allNodesToKeep) {
|
362
|
+
var existingNode, i, len, nodeId, remoteNode, results;
|
363
|
+
results = [];
|
364
|
+
for (i = 0, len = allNodesToKeep.length; i < len; i++) {
|
365
|
+
existingNode = allNodesToKeep[i];
|
366
|
+
if (!(nodeId = existingNode.getAttribute('id'))) {
|
367
|
+
throw new Error("TurboGraft refresh: Kept nodes must have an id.");
|
368
|
+
}
|
369
|
+
if (remoteNode = body.querySelector("#" + nodeId)) {
|
370
|
+
results.push(replaceNode(existingNode, remoteNode));
|
371
|
+
} else {
|
372
|
+
results.push(void 0);
|
373
|
+
}
|
374
|
+
}
|
375
|
+
return results;
|
376
|
+
};
|
377
|
+
|
378
|
+
persistStaticElements = function(body) {
|
379
|
+
var allNodesToKeep, i, len, node, nodes;
|
380
|
+
allNodesToKeep = [];
|
381
|
+
nodes = TurboGraft.querySelectorAllTGAttribute(activeDocument, 'tg-static');
|
382
|
+
for (i = 0, len = nodes.length; i < len; i++) {
|
383
|
+
node = nodes[i];
|
384
|
+
allNodesToKeep.push(node);
|
385
|
+
}
|
386
|
+
keepNodes(body, allNodesToKeep);
|
387
|
+
};
|
388
|
+
|
389
|
+
refreshAllExceptWithKeys = function(keys, body) {
|
390
|
+
var allNodesToKeep, i, j, key, len, len1, node, ref1;
|
391
|
+
allNodesToKeep = [];
|
392
|
+
for (i = 0, len = keys.length; i < len; i++) {
|
393
|
+
key = keys[i];
|
394
|
+
ref1 = TurboGraft.querySelectorAllTGAttribute(activeDocument, 'refresh', key);
|
395
|
+
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
396
|
+
node = ref1[j];
|
397
|
+
allNodesToKeep.push(node);
|
398
|
+
}
|
399
|
+
}
|
400
|
+
keepNodes(body, allNodesToKeep);
|
401
|
+
};
|
402
|
+
|
403
|
+
executeScriptTags = function() {
|
404
|
+
var i, len, ref1, script, scripts;
|
405
|
+
scripts = Array.prototype.slice.call(activeDocument.body.querySelectorAll('script:not([data-turbolinks-eval="false"])'));
|
406
|
+
for (i = 0, len = scripts.length; i < len; i++) {
|
407
|
+
script = scripts[i];
|
408
|
+
if ((ref1 = script.type) === '' || ref1 === 'text/javascript') {
|
409
|
+
executeScriptTag(script);
|
410
|
+
}
|
411
|
+
}
|
412
|
+
};
|
413
|
+
|
414
|
+
executeScriptTag = function(script) {
|
415
|
+
var attr, copy, i, len, nextSibling, parentNode, ref1;
|
416
|
+
copy = activeDocument.createElement('script');
|
417
|
+
ref1 = script.attributes;
|
418
|
+
for (i = 0, len = ref1.length; i < len; i++) {
|
419
|
+
attr = ref1[i];
|
420
|
+
copy.setAttribute(attr.name, attr.value);
|
421
|
+
}
|
422
|
+
copy.appendChild(activeDocument.createTextNode(script.innerHTML));
|
423
|
+
parentNode = script.parentNode, nextSibling = script.nextSibling;
|
424
|
+
parentNode.removeChild(script);
|
425
|
+
parentNode.insertBefore(copy, nextSibling);
|
426
|
+
};
|
427
|
+
|
428
|
+
removeNoscriptTags = function(node) {
|
429
|
+
node.innerHTML = node.innerHTML.replace(/<noscript[\S\s]*?<\/noscript>/ig, '');
|
430
|
+
return node;
|
431
|
+
};
|
432
|
+
|
433
|
+
reflectNewUrl = function(url) {
|
434
|
+
if ((url = new ComponentUrl(url)).absolute !== referer) {
|
435
|
+
Turbolinks.pushState({
|
436
|
+
turbolinks: true,
|
437
|
+
url: url.absolute
|
438
|
+
}, '', url.absolute);
|
439
|
+
}
|
440
|
+
};
|
441
|
+
|
442
|
+
rememberReferer = function() {
|
443
|
+
return referer = activeDocument.location.href;
|
444
|
+
};
|
445
|
+
|
446
|
+
Turbolinks.rememberCurrentUrl = function() {
|
447
|
+
return Turbolinks.replaceState({
|
448
|
+
turbolinks: true,
|
449
|
+
url: activeDocument.location.href
|
450
|
+
}, '', activeDocument.location.href);
|
451
|
+
};
|
452
|
+
|
453
|
+
Turbolinks.rememberCurrentState = function() {
|
454
|
+
return currentState = window.history.state;
|
455
|
+
};
|
456
|
+
|
457
|
+
recallScrollPosition = function(page) {
|
458
|
+
return window.scrollTo(page.positionX, page.positionY);
|
459
|
+
};
|
460
|
+
|
461
|
+
Turbolinks.resetScrollPosition = function() {
|
462
|
+
if (activeDocument.location.hash) {
|
463
|
+
return activeDocument.location.href = activeDocument.location.href;
|
464
|
+
} else {
|
465
|
+
return window.scrollTo(0, 0);
|
466
|
+
}
|
467
|
+
};
|
468
|
+
|
469
|
+
pageChangePrevented = function(url) {
|
470
|
+
return !triggerEvent('page:before-change', url);
|
471
|
+
};
|
472
|
+
|
473
|
+
installHistoryChangeHandler = function(event) {
|
474
|
+
var ref1;
|
475
|
+
if ((ref1 = event.state) != null ? ref1.turbolinks : void 0) {
|
476
|
+
return Turbolinks.visit(event.target.location.href);
|
477
|
+
}
|
478
|
+
};
|
479
|
+
|
480
|
+
// Delay execution of function long enough to miss the popstate event
|
481
|
+
// some browsers fire on the initial page load.
|
482
|
+
bypassOnLoadPopstate = function(fn) {
|
483
|
+
return setTimeout(fn, 500);
|
484
|
+
};
|
485
|
+
|
486
|
+
if (browserSupportsTurbolinks) {
|
487
|
+
Turbolinks.visit = fetch;
|
488
|
+
Turbolinks.rememberCurrentUrl();
|
489
|
+
Turbolinks.rememberCurrentState();
|
490
|
+
activeDocument.addEventListener('click', Click.installHandlerLast, true);
|
491
|
+
bypassOnLoadPopstate(function() {
|
492
|
+
return window.addEventListener('popstate', installHistoryChangeHandler, false);
|
493
|
+
});
|
494
|
+
} else {
|
495
|
+
Turbolinks.visit = function(url) {
|
496
|
+
return activeDocument.location.href = url;
|
497
|
+
};
|
498
|
+
}
|
499
|
+
|
500
|
+
return Turbolinks;
|
501
|
+
|
502
|
+
})();
|
503
|
+
|
504
|
+
}).call(this);
|