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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -4
  3. data/lib/assets/javascripts/turbograft/click.js +44 -0
  4. data/lib/assets/javascripts/turbograft/component_url.js +53 -0
  5. data/lib/assets/javascripts/turbograft/csrf_token.js +23 -0
  6. data/lib/assets/javascripts/turbograft/document.js +20 -0
  7. data/lib/assets/javascripts/turbograft/initializers.js +83 -0
  8. data/lib/assets/javascripts/turbograft/link.js +58 -0
  9. data/lib/assets/javascripts/turbograft/page.js +105 -0
  10. data/lib/assets/javascripts/turbograft/remote.js +248 -0
  11. data/lib/assets/javascripts/turbograft/response.js +48 -0
  12. data/lib/assets/javascripts/turbograft/turbohead.js +159 -0
  13. data/lib/assets/javascripts/turbograft/turbolinks.js +504 -0
  14. data/lib/assets/javascripts/turbograft.js +37 -0
  15. data/lib/turbograft/version.rb +1 -1
  16. metadata +27 -27
  17. data/lib/assets/javascripts/turbograft/click.coffee +0 -34
  18. data/lib/assets/javascripts/turbograft/component_url.coffee +0 -24
  19. data/lib/assets/javascripts/turbograft/csrf_token.coffee +0 -9
  20. data/lib/assets/javascripts/turbograft/document.coffee +0 -11
  21. data/lib/assets/javascripts/turbograft/initializers.coffee +0 -67
  22. data/lib/assets/javascripts/turbograft/link.coffee +0 -41
  23. data/lib/assets/javascripts/turbograft/page.coffee +0 -77
  24. data/lib/assets/javascripts/turbograft/remote.coffee +0 -179
  25. data/lib/assets/javascripts/turbograft/response.coffee +0 -31
  26. data/lib/assets/javascripts/turbograft/turbohead.coffee +0 -142
  27. data/lib/assets/javascripts/turbograft/turbolinks.coffee +0 -361
  28. 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);