turbograft 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fba255993ff4ba3b193a70109a4635ebe5c672b2baa82e788f9e94f8d1d79fe5
4
- data.tar.gz: 024e4089a512cfcaf3f3c1233eb58a44535f67be2a99257f7575c7880861fac5
3
+ metadata.gz: be4f37c2b602a9900e822397af3c01ed23d1467bc2571728824b7b7c5737b126
4
+ data.tar.gz: e3ea401831228506abb4d2c51dec872095f1a06c3ced6c8550a4a4d5950a4dd3
5
5
  SHA512:
6
- metadata.gz: e638743501bff1821d16afca2ee426081e85ff70d69b15647568c6fe2a766b089ee85f43d7138a60afe1cad2a235cdfeb25afb04c7d6dac92a42767bc125eb35
7
- data.tar.gz: f6a539ce14d10aefb28f500a2e9834a8a888851762ffa143667a6c1bc54a22948609c6498a7f611ab3208065e3e7680370de41f9ad79e038177f4a54de1a0c8b
6
+ metadata.gz: 7f637bf121bde59d9fb009595a588b6b520bf1128cd4beab8b94c28ab2196485cdeb0805290b21ab79ec6ad641c4a8ff95e20f88a9833eea051361257fe38f49
7
+ data.tar.gz: f852bf2519485f847f3f45b1f475f15296bab053b4369da8b8a503d0cc2c7884484b6030cc7a27ebc9cde4dbb4753e0c20dfb16643298a683341a40d0b945700
data/README.md CHANGED
@@ -20,7 +20,7 @@ Turbograft was built with simplicity in mind. It intends to offer the smallest a
20
20
 
21
21
  ## Status
22
22
  [![Gem Version](https://badge.fury.io/rb/turbograft.svg)](http://badge.fury.io/rb/turbograft)
23
- [![Build Status](https://travis-ci.org/Shopify/turbograft.svg?branch=master)](http://travis-ci.org/Shopify/turbograft)
23
+ [![Build](https://github.com/Shopify/turbograft/actions/workflows/build.yml/badge.svg)](https://github.com/Shopify/turbograft/actions/workflows/build.yml)
24
24
 
25
25
  ## Installation
26
26
 
@@ -189,9 +189,10 @@ There is an example app that you can boot to play with TurboGraft. Open the con
189
189
 
190
190
  When turbograft replaces or removes a node it uses native DOM API to do so. If any objects use jQuery to listen to events on a node then these objects will leak when the node is replaced because jQuery will still have references to it. To clean these up you'll need to tell jQuery that they're removed. This can be done with something like:
191
191
 
192
- ```coffeescript
193
- document.addEventListener 'page:after-node-removed', (event) ->
194
- $(event.data).remove()
192
+ ```js
193
+ document.addEventListener('page:after-node-removed', function(event) {
194
+ $(event.data).remove();
195
+ });
195
196
  ```
196
197
 
197
198
  ## Contributing
@@ -0,0 +1,44 @@
1
+ // The Click class handles clicked links, verifying if Turbolinks should
2
+ // take control by inspecting both the event and the link. If it should,
3
+ // the page change process is initiated. If not, control is passed back
4
+ // to the browser for default functionality.
5
+ window.Click = class Click {
6
+ static installHandlerLast(event) {
7
+ if (!event.defaultPrevented) {
8
+ document.removeEventListener('click', Click.handle, false);
9
+ document.addEventListener('click', Click.handle, false);
10
+ }
11
+ }
12
+
13
+ static handle(event) {
14
+ return new Click(event);
15
+ }
16
+
17
+ constructor(event) {
18
+ this.event = event;
19
+ if (this.event.defaultPrevented) { return; }
20
+ this._extractLink();
21
+ if (this._validForTurbolinks()) {
22
+ Turbolinks.visit(this.link.href);
23
+ this.event.preventDefault();
24
+ }
25
+ }
26
+
27
+ _extractLink() {
28
+ let link = this.event.target;
29
+ while (!!link.parentNode && (link.nodeName !== 'A')) { link = link.parentNode; }
30
+ if ((link.nodeName === 'A') && (link.href.length !== 0)) { this.link = new Link(link); }
31
+ }
32
+
33
+ _validForTurbolinks() {
34
+ return (this.link != null) && !this.link.shouldIgnore() && !this._nonStandardClick();
35
+ }
36
+
37
+ _nonStandardClick() {
38
+ return (this.event.which > 1) ||
39
+ this.event.metaKey ||
40
+ this.event.ctrlKey ||
41
+ this.event.shiftKey ||
42
+ this.event.altKey;
43
+ }
44
+ };
@@ -0,0 +1,53 @@
1
+ /* The ComponentUrl class converts a basic URL string into an object
2
+ * that behaves similarly to document.location.
3
+ *
4
+ * If an instance is created from a relative URL, the current document
5
+ * is used to fill in the missing attributes (protocol, host, port).
6
+ */
7
+ window.ComponentUrl = class ComponentUrl {
8
+ constructor(original, link) {
9
+ if (original == null) {
10
+ original = document.location.href;
11
+ }
12
+ if (link == null) {
13
+ link = document.createElement('a');
14
+ }
15
+ if (original.constructor === ComponentUrl) {
16
+ return original;
17
+ }
18
+ this.original = original;
19
+ this.link = link;
20
+ this._parse();
21
+ }
22
+
23
+ withoutHash() {
24
+ return this.href.replace(this.hash, '');
25
+ };
26
+
27
+ // Intention revealing function alias
28
+ withoutHashForIE10compatibility() {
29
+ return this.withoutHash();
30
+ };
31
+
32
+ hasNoHash() {
33
+ return this.hash.length === 0;
34
+ };
35
+
36
+ _parse() {
37
+ this.link.href = this.original;
38
+ this.href = this.link.href;
39
+ this.protocol = this.link.protocol;
40
+ this.host = this.link.host;
41
+ this.hostname = this.link.hostname
42
+ this.port = this.link.port;
43
+ this.pathname = this.link.pathname;
44
+ this.search = this.link.search;
45
+ this.hash = this.link.hash;
46
+ this.origin = [this.protocol, '//', this.hostname].join('');
47
+ if (this.port.length !== 0) {
48
+ this.origin += ":" + this.port;
49
+ }
50
+ this.relative = [this.pathname, this.search, this.hash].join('');
51
+ return this.absolute = this.href;
52
+ };
53
+ };
@@ -0,0 +1,23 @@
1
+ window.CSRFToken = class CSRFToken {
2
+ static get(doc) {
3
+ if (!doc) { doc = document; }
4
+ const tag = doc.querySelector('meta[name="csrf-token"]');
5
+
6
+ const object = {
7
+ node: tag
8
+ };
9
+
10
+ if (tag) {
11
+ object.token = tag.getAttribute('content');
12
+ }
13
+
14
+ return object;
15
+ }
16
+
17
+ static update(latest) {
18
+ const current = this.get();
19
+ if ((current.token != null) && (latest != null) && (current.token !== latest)) {
20
+ current.node.setAttribute('content', latest);
21
+ }
22
+ }
23
+ };
@@ -0,0 +1,20 @@
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.Document = {
7
+ create(html) {
8
+ let doc;
9
+ if (/<(html|body)/i.test(html)) {
10
+ doc = document.documentElement.cloneNode();
11
+ doc.innerHTML = html;
12
+ } else {
13
+ doc = document.documentElement.cloneNode(true);
14
+ doc.querySelector('body').innerHTML = html;
15
+ }
16
+ doc.head = doc.querySelector('head');
17
+ doc.body = doc.querySelector('body');
18
+ return doc;
19
+ }
20
+ };
@@ -0,0 +1,83 @@
1
+ /*
2
+ * decaffeinate suggestions:
3
+ * DS102: Remove unnecessary code created because of implicit returns
4
+ * DS207: Consider shorter variations of null checks
5
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
6
+ */
7
+ const hasClass = (node, search) => node.classList.contains(search);
8
+
9
+ const nodeIsDisabled = node => node.getAttribute('disabled') || hasClass(node, 'disabled');
10
+
11
+ const setupRemoteFromTarget = function(target, httpRequestType, form = null) {
12
+ const httpUrl = target.getAttribute('href') || target.getAttribute('action');
13
+
14
+ if (!httpUrl) { throw new Error(`Turbograft developer error: You did not provide a URL ('${urlAttribute}' attribute) for data-tg-remote`); }
15
+
16
+ if (TurboGraft.getTGAttribute(target, "remote-once")) {
17
+ TurboGraft.removeTGAttribute(target, "remote-once");
18
+ TurboGraft.removeTGAttribute(target, "tg-remote");
19
+ }
20
+
21
+ const options = {
22
+ httpRequestType,
23
+ httpUrl,
24
+ fullRefresh: (TurboGraft.getTGAttribute(target, 'full-refresh') != null),
25
+ refreshOnSuccess: TurboGraft.getTGAttribute(target, 'refresh-on-success'),
26
+ refreshOnSuccessExcept: TurboGraft.getTGAttribute(target, 'full-refresh-on-success-except'),
27
+ refreshOnError: TurboGraft.getTGAttribute(target, 'refresh-on-error'),
28
+ refreshOnErrorExcept: TurboGraft.getTGAttribute(target, 'full-refresh-on-error-except')
29
+ };
30
+
31
+ return new TurboGraft.Remote(options, form, target);
32
+ };
33
+
34
+ TurboGraft.handlers.remoteMethodHandler = function(ev) {
35
+ const target = ev.clickTarget;
36
+ const httpRequestType = TurboGraft.getTGAttribute(target, 'tg-remote');
37
+
38
+ if (!httpRequestType) { return; }
39
+ ev.preventDefault();
40
+
41
+ const remote = setupRemoteFromTarget(target, httpRequestType);
42
+ remote.submit();
43
+ };
44
+
45
+ TurboGraft.handlers.remoteFormHandler = function(ev) {
46
+ const {
47
+ target
48
+ } = ev;
49
+ const method = target.getAttribute('method');
50
+
51
+ if (!TurboGraft.hasTGAttribute(target, 'tg-remote')) { return; }
52
+ ev.preventDefault();
53
+
54
+ const remote = setupRemoteFromTarget(target, method, target);
55
+ remote.submit();
56
+ };
57
+
58
+ const documentListenerForButtons = function(eventType, handler, useCapture) {
59
+ if (useCapture == null) { useCapture = false; }
60
+ return document.addEventListener(eventType, function(ev) {
61
+ let {
62
+ target
63
+ } = ev;
64
+
65
+ while ((target !== document) && (target != null)) {
66
+ if ((target.nodeName === "A") || (target.nodeName === "BUTTON")) {
67
+ var isNodeDisabled = nodeIsDisabled(target);
68
+ if (isNodeDisabled) { ev.preventDefault(); }
69
+ if (!isNodeDisabled) {
70
+ ev.clickTarget = target;
71
+ handler(ev);
72
+ return;
73
+ }
74
+ }
75
+
76
+ target = target.parentNode;
77
+ }
78
+ });
79
+ };
80
+
81
+ documentListenerForButtons('click', TurboGraft.handlers.remoteMethodHandler, true);
82
+
83
+ document.addEventListener("submit", ev => TurboGraft.handlers.remoteFormHandler(ev));
@@ -0,0 +1,58 @@
1
+ /* The Link class derives from the ComponentUrl class, but is built from an
2
+ * existing link element. Provides verification functionality for Turbolinks
3
+ * to use in determining whether it should process the link when clicked.
4
+ */
5
+ window.Link = class Link extends ComponentUrl {
6
+ static initClass() {
7
+ this.HTML_EXTENSIONS = ['html'];
8
+ }
9
+
10
+ static allowExtensions() {
11
+ var extension, extensions, i, len;
12
+ extensions = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
13
+ for (i = 0, len = extensions.length; i < len; i++) {
14
+ extension = extensions[i];
15
+ Link.HTML_EXTENSIONS.push(extension);
16
+ }
17
+ return Link.HTML_EXTENSIONS;
18
+ };
19
+
20
+ constructor(link) {
21
+ if (link.constructor === Link) {
22
+ return link;
23
+ }
24
+ super(link.href, link);
25
+ }
26
+
27
+ shouldIgnore() {
28
+ return this._crossOrigin() || this._anchored() || this._nonHtml() || this._optOut() || this._target();
29
+ };
30
+
31
+ _crossOrigin() {
32
+ return this.origin !== (new ComponentUrl).origin;
33
+ };
34
+
35
+ _anchored() {
36
+ var current;
37
+ return ((this.hash && this.withoutHash()) === (current = new ComponentUrl).withoutHash()) || (this.href === current.href + '#');
38
+ };
39
+
40
+ _nonHtml() {
41
+ return this.pathname.match(/\.[a-z]+$/g) && !this.pathname.match(new RegExp("\\.(?:" + (Link.HTML_EXTENSIONS.join('|')) + ")?$", 'g'));
42
+ };
43
+
44
+ _optOut() {
45
+ var ignore, link;
46
+ link = this.link;
47
+ while (!(ignore || link === document || link === null)) {
48
+ ignore = link.getAttribute('data-no-turbolink') != null;
49
+ link = link.parentNode;
50
+ }
51
+ return ignore;
52
+ };
53
+
54
+ _target() {
55
+ return this.link.target.length !== 0;
56
+ };
57
+ };
58
+ window.Link.initClass();
@@ -0,0 +1,105 @@
1
+ /*
2
+ * decaffeinate suggestions:
3
+ * DS101: Remove unnecessary use of Array.from
4
+ * DS102: Remove unnecessary code created because of implicit returns
5
+ * DS205: Consider reworking code to avoid use of IIFEs
6
+ * DS207: Consider shorter variations of null checks
7
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
8
+ */
9
+ if (!window.Page) { window.Page = {}; }
10
+
11
+ Page.visit = function(url, opts) {
12
+ if (opts == null) { opts = {}; }
13
+ if (opts.reload) {
14
+ return window.location = url;
15
+ } else {
16
+ return Turbolinks.visit(url);
17
+ }
18
+ };
19
+
20
+ Page.refresh = function(options, callback) {
21
+ let xhr;
22
+ if (options == null) { options = {}; }
23
+ const newUrl = (() => {
24
+ if (options.url) {
25
+ return options.url;
26
+ } else if (options.queryParams) {
27
+ let paramString = $.param(options.queryParams);
28
+ if (paramString) { paramString = `?${paramString}`; }
29
+ return location.pathname + paramString;
30
+ } else {
31
+ return location.href;
32
+ }
33
+ })();
34
+
35
+ const turboOptions = {
36
+ partialReplace: true,
37
+ exceptKeys: options.exceptKeys,
38
+ onlyKeys: options.onlyKeys,
39
+ updatePushState: options.updatePushState,
40
+ callback
41
+ };
42
+
43
+ if ((xhr = options.response)) {
44
+ return Turbolinks.loadPage(null, xhr, turboOptions);
45
+ } else {
46
+ return Turbolinks.visit(newUrl, turboOptions);
47
+ }
48
+ };
49
+
50
+ Page.open = function() {
51
+ return window.open(...arguments);
52
+ };
53
+
54
+ // Providing hooks for objects to set up destructors:
55
+ let onReplaceCallbacks = [];
56
+
57
+ // e.g., Page.onReplace(node, unbindListenersFnc)
58
+ // unbindListenersFnc will be called if the node in question is partially replaced
59
+ // or if a full replace occurs. It will be called only once
60
+ Page.onReplace = function(node, callback) {
61
+ if (!node || !callback) { throw new Error("Page.onReplace: Node and callback must both be specified"); }
62
+ if (!isFunction(callback)) { throw new Error("Page.onReplace: Callback must be a function"); }
63
+ return onReplaceCallbacks.push({node, callback});
64
+ };
65
+
66
+ // option C from http://jsperf.com/alternative-isfunction-implementations
67
+ var isFunction = object => !!(object && object.constructor && object.call && object.apply);
68
+
69
+ // roughly based on http://davidwalsh.name/check-parent-node (note, OP is incorrect)
70
+ const contains = function(parentNode, childNode) {
71
+ if (parentNode.contains) {
72
+ return parentNode.contains(childNode);
73
+ } else { // old browser compatability
74
+ return !!((parentNode === childNode) || (parentNode.compareDocumentPosition(childNode) & Node.DOCUMENT_POSITION_CONTAINED_BY));
75
+ }
76
+ };
77
+
78
+ document.addEventListener('page:before-partial-replace', function(event) {
79
+ const replacedNodes = event.data;
80
+
81
+ const unprocessedOnReplaceCallbacks = [];
82
+ for (var entry of Array.from(onReplaceCallbacks)) {
83
+ var fired = false;
84
+ for (var replacedNode of Array.from(replacedNodes)) {
85
+ if (contains(replacedNode, entry.node)) {
86
+ entry.callback();
87
+ fired = true;
88
+ break;
89
+ }
90
+ }
91
+
92
+ if (!fired) {
93
+ unprocessedOnReplaceCallbacks.push(entry);
94
+ }
95
+ }
96
+
97
+ return onReplaceCallbacks = unprocessedOnReplaceCallbacks;
98
+ });
99
+
100
+ document.addEventListener('page:before-replace', function(event) {
101
+ for (var entry of Array.from(onReplaceCallbacks)) {
102
+ entry.callback();
103
+ }
104
+ return onReplaceCallbacks = [];
105
+ });
@@ -0,0 +1,248 @@
1
+ /*
2
+ * decaffeinate suggestions:
3
+ * DS101: Remove unnecessary use of Array.from
4
+ * DS102: Remove unnecessary code created because of implicit returns
5
+ * DS205: Consider reworking code to avoid use of IIFEs
6
+ * DS207: Consider shorter variations of null checks
7
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
8
+ */
9
+ TurboGraft.Remote = class Remote {
10
+ constructor(opts, form, target) {
11
+
12
+ this.onSuccess = this.onSuccess.bind(this);
13
+ this.onError = this.onError.bind(this);
14
+ this.opts = opts;
15
+ this.initiator = form || target;
16
+
17
+ this.actualRequestType = (this.opts.httpRequestType != null ? this.opts.httpRequestType.toLowerCase() : undefined) === 'get' ? 'GET' : 'POST';
18
+ this.useNativeEncoding = this.opts.useNativeEncoding;
19
+
20
+ this.formData = this.createPayload(form);
21
+
22
+ if (this.opts.refreshOnSuccess) { this.refreshOnSuccess = this.opts.refreshOnSuccess.split(" "); }
23
+ if (this.opts.refreshOnSuccessExcept) { this.refreshOnSuccessExcept = this.opts.refreshOnSuccessExcept.split(" "); }
24
+ if (this.opts.refreshOnError) { this.refreshOnError = this.opts.refreshOnError.split(" "); }
25
+ if (this.opts.refreshOnErrorExcept) { this.refreshOnErrorExcept = this.opts.refreshOnErrorExcept.split(" "); }
26
+
27
+ const xhr = new XMLHttpRequest;
28
+ if (this.actualRequestType === 'GET') {
29
+ const url = this.formData ? this.opts.httpUrl + `?${this.formData}` : this.opts.httpUrl;
30
+ xhr.open(this.actualRequestType, url, true);
31
+ } else {
32
+ xhr.open(this.actualRequestType, this.opts.httpUrl, true);
33
+ }
34
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
35
+ xhr.setRequestHeader('Accept', 'text/html, application/xhtml+xml, application/xml');
36
+ if (this.contentType) { xhr.setRequestHeader("Content-Type", this.contentType); }
37
+ xhr.setRequestHeader('X-XHR-Referer', document.location.href);
38
+
39
+ const csrfToken = CSRFToken.get().token;
40
+ if (csrfToken) { xhr.setRequestHeader('X-CSRF-Token', csrfToken); }
41
+
42
+ triggerEventFor('turbograft:remote:init', this.initiator, {xhr, initiator: this.initiator});
43
+
44
+ xhr.addEventListener('loadstart', () => {
45
+ return triggerEventFor('turbograft:remote:start', this.initiator,
46
+ {xhr});
47
+ });
48
+
49
+ xhr.addEventListener('error', this.onError);
50
+ xhr.addEventListener('load', event => {
51
+ if (xhr.status < 400) {
52
+ return this.onSuccess(event);
53
+ } else {
54
+ return this.onError(event);
55
+ }
56
+ });
57
+
58
+ xhr.addEventListener('loadend', () => {
59
+ if (typeof this.opts.done === 'function') {
60
+ this.opts.done();
61
+ }
62
+ return triggerEventFor('turbograft:remote:always', this.initiator, {
63
+ initiator: this.initiator,
64
+ xhr
65
+ }
66
+ );
67
+ });
68
+
69
+ this.xhr = xhr;
70
+ }
71
+
72
+ submit() {
73
+ return this.xhr.send(this.formData);
74
+ }
75
+
76
+ createPayload(form) {
77
+ let formData;
78
+ if (form) {
79
+ if (this.useNativeEncoding || (form.querySelectorAll("[type='file'][name]").length > 0)) {
80
+ formData = this.nativeEncodeForm(form);
81
+ } else { // for much smaller payloads
82
+ formData = this.uriEncodeForm(form);
83
+ }
84
+ } else {
85
+ formData = '';
86
+ }
87
+
88
+ if (!(formData instanceof FormData)) {
89
+ this.contentType = "application/x-www-form-urlencoded; charset=UTF-8";
90
+ if ((formData.indexOf("_method") === -1) && this.opts.httpRequestType && (this.actualRequestType !== 'GET')) { formData = this.formAppend(formData, "_method", this.opts.httpRequestType); }
91
+ }
92
+
93
+ return formData;
94
+ }
95
+
96
+ formAppend(uriEncoded, key, value) {
97
+ if (uriEncoded.length) { uriEncoded += "&"; }
98
+ return uriEncoded += `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
99
+ }
100
+
101
+ uriEncodeForm(form) {
102
+ let formData = "";
103
+ this._iterateOverFormInputs(form, input => {
104
+ return formData = this.formAppend(formData, input.name, input.value);
105
+ });
106
+ return formData;
107
+ }
108
+
109
+ formDataAppend(formData, input) {
110
+ if (input.type === 'file') {
111
+ for (var file of Array.from(input.files)) {
112
+ formData.append(input.name, file);
113
+ }
114
+ } else {
115
+ formData.append(input.name, input.value);
116
+ }
117
+ return formData;
118
+ }
119
+
120
+ nativeEncodeForm(form) {
121
+ let formData = new FormData;
122
+ this._iterateOverFormInputs(form, input => {
123
+ return formData = this.formDataAppend(formData, input);
124
+ });
125
+ return formData;
126
+ }
127
+
128
+ _iterateOverFormInputs(form, callback) {
129
+ const inputs = this._enabledInputs(form);
130
+ return (() => {
131
+ const result = [];
132
+ for (var input of Array.from(inputs)) {
133
+ var inputEnabled = !input.disabled;
134
+ var radioOrCheck = ((input.type === 'checkbox') || (input.type === 'radio'));
135
+
136
+ if (inputEnabled && input.name) {
137
+ if ((radioOrCheck && input.checked) || !radioOrCheck) {
138
+ result.push(callback(input));
139
+ } else {
140
+ result.push(undefined);
141
+ }
142
+ } else {
143
+ result.push(undefined);
144
+ }
145
+ }
146
+ return result;
147
+ })();
148
+ }
149
+
150
+ _enabledInputs(form) {
151
+ const selector = "input:not([type='reset']):not([type='button']):not([type='submit']):not([type='image']), select, textarea";
152
+ const inputs = Array.prototype.slice.call(form.querySelectorAll(selector));
153
+ const disabledNodes = Array.prototype.slice.call(TurboGraft.querySelectorAllTGAttribute(form, 'tg-remote-noserialize'));
154
+
155
+ if (!disabledNodes.length) { return inputs; }
156
+
157
+ let disabledInputs = disabledNodes;
158
+ for (var node of Array.from(disabledNodes)) {
159
+ disabledInputs = disabledInputs.concat(Array.prototype.slice.call(node.querySelectorAll(selector)));
160
+ }
161
+
162
+ const enabledInputs = [];
163
+ for (var input of Array.from(inputs)) {
164
+ if (disabledInputs.indexOf(input) < 0) {
165
+ enabledInputs.push(input);
166
+ }
167
+ }
168
+ return enabledInputs;
169
+ }
170
+
171
+ onSuccess(ev) {
172
+ let redirect;
173
+ if (typeof this.opts.success === 'function') {
174
+ this.opts.success();
175
+ }
176
+
177
+ const xhr = ev.target;
178
+ triggerEventFor('turbograft:remote:success', this.initiator, {
179
+ initiator: this.initiator,
180
+ xhr
181
+ }
182
+ );
183
+
184
+ if (redirect = xhr.getResponseHeader('X-Next-Redirect')) {
185
+ Page.visit(redirect, {reload: true});
186
+ return;
187
+ }
188
+
189
+ if (!TurboGraft.hasTGAttribute(this.initiator, 'tg-remote-norefresh')) {
190
+ if (this.opts.fullRefresh && this.refreshOnSuccess) {
191
+ return Page.refresh({onlyKeys: this.refreshOnSuccess});
192
+ } else if (this.opts.fullRefresh) {
193
+ return Page.refresh();
194
+ } else if (this.refreshOnSuccess) {
195
+ return Page.refresh({
196
+ response: xhr,
197
+ onlyKeys: this.refreshOnSuccess
198
+ });
199
+ } else if (this.refreshOnSuccessExcept) {
200
+ return Page.refresh({
201
+ response: xhr,
202
+ exceptKeys: this.refreshOnSuccessExcept
203
+ });
204
+ } else {
205
+ return Page.refresh({
206
+ response: xhr
207
+ });
208
+ }
209
+ }
210
+ }
211
+
212
+ onError(ev) {
213
+ if (typeof this.opts.fail === 'function') {
214
+ this.opts.fail();
215
+ }
216
+
217
+ const xhr = ev.target;
218
+ triggerEventFor('turbograft:remote:fail', this.initiator, {
219
+ initiator: this.initiator,
220
+ xhr
221
+ }
222
+ );
223
+
224
+ if (TurboGraft.hasTGAttribute(this.initiator, 'tg-remote-norefresh')) {
225
+ return triggerEventFor('turbograft:remote:fail:unhandled', this.initiator,
226
+ {xhr});
227
+ } else {
228
+ if (this.opts.fullRefresh && this.refreshOnError) {
229
+ return Page.refresh({onlyKeys: this.refreshOnError});
230
+ } else if (this.opts.fullRefresh) {
231
+ return Page.refresh();
232
+ } else if (this.refreshOnError) {
233
+ return Page.refresh({
234
+ response: xhr,
235
+ onlyKeys: this.refreshOnError
236
+ });
237
+ } else if (this.refreshOnErrorExcept) {
238
+ return Page.refresh({
239
+ response: xhr,
240
+ exceptKeys: this.refreshOnErrorExcept
241
+ });
242
+ } else {
243
+ return triggerEventFor('turbograft:remote:fail:unhandled', this.initiator,
244
+ {xhr});
245
+ }
246
+ }
247
+ }
248
+ };