turbograft 0.4.8 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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);
|