render_async 2.1.0 → 2.1.11

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.
@@ -5,36 +5,33 @@
5
5
  <%= placeholder %>
6
6
  </<%= html_element_name %>>
7
7
 
8
- <% content_for :render_async do %>
8
+ <% content_for content_for_name do %>
9
9
  <%= javascript_tag html_options do %>
10
+ <% locals = { container_id: container_id,
11
+ replace_container: replace_container,
12
+ path: path,
13
+ method: method,
14
+ data: data,
15
+ event_name: event_name,
16
+ toggle: toggle,
17
+ headers: headers,
18
+ error_message: error_message,
19
+ error_event_name: error_event_name,
20
+ retry_count: retry_count,
21
+ retry_count_header: retry_count_header,
22
+ retry_delay: retry_delay,
23
+ interval: interval,
24
+ turbolinks: RenderAsync.configuration.turbolinks,
25
+ turbo: RenderAsync.configuration.turbo} %>
26
+
10
27
  <% if RenderAsync.configuration.jquery %>
11
28
  <%= render partial: 'render_async/request_jquery',
12
29
  formats: [:js],
13
- locals: { container_id: container_id,
14
- path: path,
15
- method: method,
16
- data: data,
17
- event_name: event_name,
18
- headers: headers,
19
- error_message: error_message,
20
- error_event_name: error_event_name,
21
- retry_count: retry_count,
22
- interval: interval,
23
- turbolinks: RenderAsync.configuration.turbolinks } %>
30
+ locals: locals %>
24
31
  <% else %>
25
32
  <%= render partial: 'render_async/request_vanilla',
26
33
  formats: [:js],
27
- locals: { container_id: container_id,
28
- path: path,
29
- method: method,
30
- data: data,
31
- event_name: event_name,
32
- headers: headers,
33
- error_message: error_message,
34
- error_event_name: error_event_name,
35
- retry_count: retry_count,
36
- interval: interval,
37
- turbolinks: RenderAsync.configuration.turbolinks } %>
34
+ locals: locals %>
38
35
  <% end %>
39
36
  <% end %>
40
37
  <% end %>
@@ -5,12 +5,49 @@ if (window.jQuery) {
5
5
  return;
6
6
  }
7
7
  <% end %>
8
+ <% if turbo %>
9
+ if (document.documentElement.hasAttribute("data-turbo-preview")) {
10
+ return;
11
+ }
12
+ <% end %>
13
+ function createEvent(name, container) {
14
+ var event = undefined;
15
+ if (typeof(Event) === 'function') {
16
+ event = new Event(name);
17
+ } else {
18
+ event = document.createEvent('Event');
19
+ event.initEvent(name, true, true);
20
+ }
21
+ event.container = container
22
+ return event;
23
+ }
8
24
 
9
- var _listener = function(currentRetryCount) {
25
+ function _runAfterDocumentLoaded(callback) {
26
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
27
+ // Handle a case where nested partials get loaded after the document loads
28
+ callback();
29
+ } else {
30
+ <% if turbolinks %>
31
+ $(document).one('turbolinks:load', callback);
32
+ <% elsif turbo %>
33
+ $(document).one('turbo:load', callback);
34
+ <% else %>
35
+ $(document).ready(callback);
36
+ <% end %>
37
+ }
38
+ }
39
+
40
+ function _makeRequest(currentRetryCount) {
10
41
  var headers = <%= headers.to_json.html_safe %>;
11
- var csrfTokenElement = document.querySelector('meta[name="csrf-token"]')
42
+ var csrfTokenElement = document.querySelector('meta[name="csrf-token"]');
12
43
  if (csrfTokenElement)
13
- headers['X-CSRF-Token'] = csrfTokenElement.content
44
+ headers['X-CSRF-Token'] = csrfTokenElement.content;
45
+
46
+ <% if retry_count_header %>
47
+ if (typeof(currentRetryCount) === 'number') {
48
+ headers['<%= retry_count_header.html_safe %>'] = currentRetryCount;
49
+ }
50
+ <% end %>
14
51
 
15
52
  $.ajax({
16
53
  url: '<%= path.html_safe %>',
@@ -18,21 +55,23 @@ if (window.jQuery) {
18
55
  data: "<%= escape_javascript(data.to_s.html_safe) %>",
19
56
  headers: headers
20
57
  }).done(function(response) {
21
- <% if interval %>
22
- $("#<%= container_id %>").empty();
23
- $("#<%= container_id %>").append(response);
58
+ var container = $("#<%= container_id %>");
59
+
60
+ // If user navigated away before the request completed
61
+ if (!container.length) return;
62
+
63
+ <% if !interval && replace_container %>
64
+ container.replaceWith(response);
24
65
  <% else %>
25
- $("#<%= container_id %>").replaceWith(response);
66
+ container.empty();
67
+ container.append(response);
26
68
  <% end %>
27
69
 
70
+ var loadEvent = createEvent('render_async_load', container);
71
+ document.dispatchEvent(loadEvent);
72
+
28
73
  <% if event_name.present? %>
29
- var event = undefined;
30
- if (typeof(Event) === 'function') {
31
- event = new Event("<%= event_name %>");
32
- } else {
33
- event = document.createEvent('Event');
34
- event.initEvent('<%= event_name %>', true, true);
35
- }
74
+ var event = createEvent("<%= event_name %>", container)
36
75
  document.dispatchEvent(event);
37
76
  <% end %>
38
77
  }).fail(function(response) {
@@ -43,48 +82,118 @@ if (window.jQuery) {
43
82
 
44
83
  if (skipErrorMessage) return;
45
84
 
46
- $("#<%= container_id %>").replaceWith('<%= error_message.try(:html_safe) %>');
85
+ var container = $("#<%= container_id %>");
86
+ if (!container.length) return;
47
87
 
48
- <% if error_event_name.present? %>
49
- var event = undefined;
50
- if (typeof(Event) === 'function') {
51
- event = new Event("<%= error_event_name %>");
52
- } else {
53
- event = document.createEvent('Event');
54
- event.initEvent('<%= error_event_name %>', true, true);
55
- }
56
- document.dispatchEvent(event);
57
- <% end %>
88
+ container.replaceWith("<%= error_message.try(:html_safe) %>");
89
+
90
+ var errorEvent = createEvent(
91
+ "<%= error_event_name || 'render_async_error' %>",
92
+ container
93
+ )
94
+ errorEvent.retryCount = currentRetryCount
95
+
96
+ document.dispatchEvent(errorEvent);
58
97
  });
59
98
  };
60
99
 
61
100
  <% if retry_count > 0 %>
62
- var retry = function(currentRetryCount) {
101
+ var _retryMakeRequest = _makeRequest
102
+
103
+ <% if retry_delay %>
104
+ _retryMakeRequest = function(currentRetryCount) {
105
+ setTimeout(function() {
106
+ _makeRequest(currentRetryCount)
107
+ }, <%= retry_delay %>)
108
+ }
109
+ <% else %>
110
+ _retryMakeRequest = _makeRequest
111
+ <% end %>
112
+
113
+ function retry(currentRetryCount) {
63
114
  if (typeof(currentRetryCount) === 'number') {
64
115
  if (currentRetryCount >= <%= retry_count %>)
65
116
  return false;
66
117
 
67
- _listener(currentRetryCount + 1);
118
+ _retryMakeRequest(currentRetryCount + 1);
68
119
  return true;
69
120
  }
70
121
 
71
- _listener(1);
122
+ _retryMakeRequest(1);
72
123
  return true;
73
124
  }
74
125
  <% end %>
75
126
 
127
+ var _renderAsyncFunction = _makeRequest;
128
+
129
+ var _interval;
130
+ <% if interval %>
131
+ var _renderAsyncFunction = function() {
132
+ // If interval is already set, return
133
+ if (typeof(_interval) === 'number') return
134
+
135
+ _makeRequest();
136
+ _interval = setInterval(_makeRequest, <%= interval %>);
137
+ }
138
+
139
+ var _clearRenderAsyncInterval = function() {
140
+ if (typeof(_interval) === 'number'){
141
+ clearInterval(_interval)
142
+ _interval = undefined;
143
+ }
144
+ }
145
+
146
+ function _setUpControlEvents() {
147
+ var container = $("#<%= container_id %>");
148
+
149
+ // Register a stop polling event on the container
150
+ $(container).on('async-stop', _clearRenderAsyncInterval)
151
+
152
+ // Register a start polling event on the container
153
+ $(container).on('async-start', _renderAsyncFunction)
154
+ }
155
+
156
+ _runAfterDocumentLoaded(_setUpControlEvents)
157
+
76
158
  <% if turbolinks %>
77
- $(document).one('turbolinks:load', _listener);
78
- <% elsif interval %>
79
- var _intervalFunction = function() {
80
- _listener();
81
- setInterval(_listener, <%= interval %>);
159
+ $(document).one('turbolinks:visit', _clearRenderAsyncInterval);
160
+ <% end %>
161
+ <% if turbo %>
162
+ $(document).one('turbo:visit', _clearRenderAsyncInterval);
163
+ <% end %>
164
+ <% end %>
165
+
166
+ <% if !replace_container %>
167
+ function _setUpRefreshEvent() {
168
+ var container = $("#<%= container_id %>");
169
+
170
+ $(container).on('refresh', _renderAsyncFunction)
82
171
  }
83
- $(document).ready(_intervalFunction);
84
- <% else %>
85
- $(document).ready(_listener);
172
+
173
+ _runAfterDocumentLoaded(_setUpRefreshEvent)
174
+ <% end %>
175
+
176
+ <% if toggle %>
177
+ function _setUpToggle() {
178
+ $(document).<%= toggle[:once] ? 'one' : 'on' %>('<%= toggle[:event] || 'click' %>', '<%= toggle[:selector] %>', function(event) {
179
+ if (typeof(_interval) === 'number') {
180
+ clearInterval(_interval);
181
+ _interval = undefined;
182
+ } else {
183
+ _renderAsyncFunction();
184
+ }
185
+ });
186
+
187
+ <% if toggle[:start] %>
188
+ _renderAsyncFunction()
189
+ <% end %>
190
+ }
191
+
192
+ _runAfterDocumentLoaded(_setUpToggle);
193
+ <% elsif !toggle %>
194
+ _runAfterDocumentLoaded(_renderAsyncFunction)
86
195
  <% end %>
87
196
  }(jQuery));
88
197
  } else {
89
- console.warn("Looks like you've enabled jQuery for render_async, but jQuery is not defined");
198
+ console.warn("Looks like you've enabled jQuery for render_async, but jQuery is not defined on the window object");
90
199
  };
@@ -4,8 +4,40 @@
4
4
  return;
5
5
  }
6
6
  <% end %>
7
-
8
- var _listener = function(currentRetryCount) {
7
+ <% if turbo %>
8
+ if (document.documentElement.hasAttribute("data-turbo-preview")) {
9
+ return;
10
+ }
11
+ <% end %>
12
+ function createEvent(name, container) {
13
+ var event = undefined;
14
+ if (typeof(Event) === 'function') {
15
+ event = new Event(name);
16
+ } else {
17
+ event = document.createEvent('Event');
18
+ event.initEvent(name, true, true);
19
+ }
20
+ event.container = container
21
+ return event;
22
+ }
23
+
24
+ function _runAfterDocumentLoaded(callback) {
25
+ <% if turbolinks %>
26
+ document.addEventListener("turbolinks:load", function(e) {
27
+ e.target.removeEventListener(e.type, arguments.callee);
28
+ callback();
29
+ });
30
+ <% elsif turbo %>
31
+ document.addEventListener("turbo:load", function(e) {
32
+ e.target.removeEventListener(e.type, arguments.callee);
33
+ callback();
34
+ });
35
+ <% else %>
36
+ document.addEventListener("DOMContentLoaded", callback);
37
+ <% end %>
38
+ }
39
+
40
+ function _makeRequest(currentRetryCount) {
9
41
  var request = new XMLHttpRequest();
10
42
  var asyncRequest = true;
11
43
  var SUCCESS = 200;
@@ -14,32 +46,41 @@
14
46
  request.open('<%= method %>', '<%= path.html_safe %>', asyncRequest);
15
47
 
16
48
  var headers = <%= headers.to_json.html_safe %>;
17
- var csrfTokenElement = document.querySelector('meta[name="csrf-token"]')
49
+ var csrfTokenElement = document.querySelector('meta[name="csrf-token"]');
18
50
  if (csrfTokenElement)
19
- headers['X-CSRF-Token'] = csrfTokenElement.content
51
+ headers['X-CSRF-Token'] = csrfTokenElement.content;
52
+
53
+ <% if retry_count_header %>
54
+ if (typeof(currentRetryCount) === 'number') {
55
+ headers['<%= retry_count_header.html_safe %>'] = currentRetryCount;
56
+ }
57
+ <% end %>
20
58
 
21
59
  Object.keys(headers).map(function(key) {
22
60
  request.setRequestHeader(key, headers[key]);
23
61
  });
24
62
 
63
+ request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
64
+
25
65
  request.onreadystatechange = function() {
26
66
  if (request.readyState === 4) {
27
67
  if (request.status >= SUCCESS && request.status < ERROR) {
28
68
  var container = document.getElementById('<%= container_id %>');
29
- <% if interval %>
30
- container.innerHTML = request.response;
31
- <% else %>
69
+
70
+ // If user navigated away before the request completed
71
+ if (!container) return;
72
+
73
+ <% if !interval && replace_container %>
32
74
  container.outerHTML = request.response;
75
+ <% else %>
76
+ container.innerHTML = request.response;
33
77
  <% end %>
34
78
 
79
+ var loadEvent = createEvent('render_async_load', container);
80
+ document.dispatchEvent(loadEvent);
81
+
35
82
  <% if event_name.present? %>
36
- var event = undefined;
37
- if (typeof(Event) === 'function') {
38
- event = new Event("<%= event_name %>");
39
- } else {
40
- event = document.createEvent('Event');
41
- event.initEvent('<%= event_name %>', true, true);
42
- }
83
+ var event = createEvent('<%= event_name %>', container);
43
84
  document.dispatchEvent(event);
44
85
  <% end %>
45
86
  } else {
@@ -51,18 +92,17 @@
51
92
  if (skipErrorMessage) return;
52
93
 
53
94
  var container = document.getElementById('<%= container_id %>');
95
+ if (!container) return;
96
+
54
97
  container.outerHTML = '<%= error_message.try(:html_safe) %>';
55
98
 
56
- <% if error_event_name.present? %>
57
- var event = undefined;
58
- if (typeof(Event) === 'function') {
59
- event = new Event("<%= error_event_name %>");
60
- } else {
61
- event = document.createEvent('Event');
62
- event.initEvent('<%= error_event_name %>', true, true);
63
- }
64
- document.dispatchEvent(event);
65
- <% end %>
99
+ var errorEvent = createEvent(
100
+ "<%= error_event_name || 'render_async_error' %>",
101
+ container
102
+ );
103
+ errorEvent.retryCount = currentRetryCount
104
+
105
+ document.dispatchEvent(errorEvent);
66
106
  }
67
107
  }
68
108
  };
@@ -72,32 +112,106 @@
72
112
  };
73
113
 
74
114
  <% if retry_count > 0 %>
75
- var retry = function(currentRetryCount) {
115
+
116
+ <% if retry_delay %>
117
+ var _retryMakeRequest = function(currentRetryCount) {
118
+ setTimeout(function() {
119
+ _makeRequest(currentRetryCount)
120
+ }, <%= retry_delay %>)
121
+ }
122
+ <% else %>
123
+ var _retryMakeRequest = _makeRequest
124
+ <% end %>
125
+
126
+ function retry(currentRetryCount) {
76
127
  if (typeof(currentRetryCount) === 'number') {
77
128
  if (currentRetryCount >= <%= retry_count %>)
78
129
  return false;
79
130
 
80
- _listener(currentRetryCount + 1);
131
+ _retryMakeRequest(currentRetryCount + 1);
81
132
  return true;
82
133
  }
83
134
 
84
- _listener(1);
135
+ _retryMakeRequest(1);
85
136
  return true;
86
137
  }
87
138
  <% end %>
88
139
 
140
+ var _renderAsyncFunction = _makeRequest;
141
+
142
+ var _interval;
143
+ <% if interval %>
144
+ var _renderAsyncFunction = function() {
145
+ // If interval is already set, return
146
+ if (typeof(_interval) === 'number') return
147
+
148
+ _makeRequest();
149
+ _interval = setInterval(_makeRequest, <%= interval %>);
150
+ }
151
+
152
+ var _clearRenderAsyncInterval = function() {
153
+ if (typeof(_interval) === 'number'){
154
+ clearInterval(_interval)
155
+ _interval = undefined;
156
+ }
157
+ }
158
+
159
+ function _setUpControlEvents() {
160
+ var container = document.getElementById('<%= container_id %>');
161
+
162
+ // Register a polling stop event on the container
163
+ container.addEventListener("async-stop", _clearRenderAsyncInterval)
164
+
165
+ // Register a start polling event on the container
166
+ container.addEventListener("async-start", _renderAsyncFunction)
167
+ }
168
+
169
+ _runAfterDocumentLoaded(_setUpControlEvents)
170
+
89
171
  <% if turbolinks %>
90
- document.addEventListener("turbolinks:load", function (e) {
91
- e.target.removeEventListener(e.type, arguments.callee);
92
- _listener.call(this);
93
- });
94
- <% elsif interval %>
95
- var _intervalFunction = function() {
96
- _listener();
97
- setInterval(_listener, <%= interval %>);
172
+ document.addEventListener("turbolinks:visit", _clearRenderAsyncInterval)
173
+ <% end %>
174
+ <% if turbo %>
175
+ document.addEventListener("turbo:visit", _clearRenderAsyncInterval)
176
+ <% end %>
177
+ <% end %>
178
+
179
+ <% if !replace_container %>
180
+ function _setUpRefreshEvent() {
181
+ var container = document.getElementById('<%= container_id %>');
182
+
183
+ container.addEventListener('refresh', _renderAsyncFunction)
98
184
  }
99
- document.addEventListener("DOMContentLoaded", _intervalFunction);
100
- <% else %>
101
- document.addEventListener("DOMContentLoaded", _listener);
185
+
186
+ _runAfterDocumentLoaded(_setUpRefreshEvent)
187
+ <% end %>
188
+
189
+ <% if toggle %>
190
+ function _setUpToggle() {
191
+ var selectors = document.querySelectorAll('<%= toggle[:selector] %>');
192
+ var handler = function(event) {
193
+ if (typeof(_interval) === 'number') {
194
+ clearInterval(_interval);
195
+ _interval = undefined;
196
+ } else {
197
+ _renderAsyncFunction();
198
+ }
199
+ <% if toggle[:once] %>
200
+ this.removeEventListener(event.type, handler);
201
+ <% end %>
202
+ };
203
+
204
+ <% if toggle[:start] %>
205
+ _renderAsyncFunction()
206
+ <% end %>
207
+
208
+ for (var i = 0; i < selectors.length; ++i) {
209
+ selectors[i].addEventListener('<%= toggle[:event] || 'click' %>', handler)
210
+ }
211
+ }
212
+
213
+ _runAfterDocumentLoaded(_setUpToggle);
214
+ <% elsif !toggle %>
215
+ _runAfterDocumentLoaded(_renderAsyncFunction);
102
216
  <% end %>
103
217
  })();
@@ -11,3 +11,10 @@ cd spec/fixtures/rails-5-base-app
11
11
  ls
12
12
  bundle install
13
13
  bundle exec cucumber
14
+
15
+ cd ../../../spec/fixtures/rails-6-base-app
16
+ ls
17
+ bundle install
18
+ yarn install
19
+ RAILS_ENV=test bundle exec rails webpacker:compile
20
+ bundle exec cucumber
@@ -1,10 +1,13 @@
1
1
  module RenderAsync
2
2
  class Configuration
3
- attr_accessor :jquery, :turbolinks
3
+ attr_accessor :jquery, :turbolinks, :turbo, :replace_container, :nonces
4
4
 
5
5
  def initialize
6
6
  @jquery = false
7
7
  @turbolinks = false
8
+ @turbo = false
9
+ @replace_container = true
10
+ @nonces = false
8
11
  end
9
12
  end
10
13
  end
@@ -1,3 +1,3 @@
1
1
  module RenderAsync
2
- VERSION = "2.1.0".freeze
2
+ VERSION = "2.1.11".freeze
3
3
  end
@@ -2,7 +2,6 @@ require 'securerandom'
2
2
 
3
3
  module RenderAsync
4
4
  module ViewHelper
5
-
6
5
  def render_async_cache_key(path)
7
6
  "render_async_#{path}"
8
7
  end
@@ -18,39 +17,80 @@ module RenderAsync
18
17
  end
19
18
 
20
19
  def render_async(path, options = {}, &placeholder)
21
- html_element_name = options.delete(:html_element_name) || 'div'
22
- container_id = options.delete(:container_id) || generate_container_id
23
- container_class = options.delete(:container_class)
24
20
  event_name = options.delete(:event_name)
25
21
  placeholder = capture(&placeholder) if block_given?
26
- method = options.delete(:method) || 'GET'
27
- data = options.delete(:data)
28
- headers = options.delete(:headers) || {}
29
- error_message = options.delete(:error_message)
30
- error_event_name = options.delete(:error_event_name)
31
- retry_count = options.delete(:retry_count) || 0
32
- interval = options.delete(:interval)
33
-
34
- render 'render_async/render_async', html_element_name: html_element_name,
35
- container_id: container_id,
36
- container_class: container_class,
22
+
23
+ render 'render_async/render_async', **container_element_options(options),
37
24
  path: path,
38
- html_options: options,
25
+ html_options: html_options(options),
39
26
  event_name: event_name,
40
27
  placeholder: placeholder,
41
- method: method,
42
- data: data,
43
- headers: headers,
44
- error_message: error_message,
45
- error_event_name: error_event_name,
46
- retry_count: retry_count,
47
- interval: interval
28
+ **request_options(options),
29
+ **error_handling_options(options),
30
+ **retry_options(options),
31
+ **polling_options(options),
32
+ **content_for_options(options)
48
33
  end
49
34
 
50
35
  private
51
36
 
37
+ def container_element_options(options)
38
+ { html_element_name: options[:html_element_name] || 'div',
39
+ container_id: options[:container_id] || generate_container_id,
40
+ container_class: options[:container_class],
41
+ replace_container: replace_container(options) }
42
+ end
43
+
44
+ def html_options(options)
45
+ set_options = options.delete(:html_options) || {}
46
+
47
+ set_options[:nonce] = configuration.nonces if set_options[:nonce].nil?
48
+
49
+ set_options
50
+ end
51
+
52
+ def request_options(options)
53
+ { method: options[:method] || 'GET',
54
+ data: options[:data],
55
+ headers: options[:headers] || {} }
56
+ end
57
+
58
+ def error_handling_options(options)
59
+ { error_message: options[:error_message],
60
+ error_event_name: options[:error_event_name] }
61
+ end
62
+
63
+ def retry_options(options)
64
+ {
65
+ retry_count: options.delete(:retry_count) || 0,
66
+ retry_count_header: options.delete(:retry_count_header),
67
+ retry_delay: options.delete(:retry_delay)
68
+ }
69
+ end
70
+
71
+ def polling_options(options)
72
+ { interval: options[:interval],
73
+ toggle: options[:toggle] }
74
+ end
75
+
76
+ def content_for_options(options)
77
+ {
78
+ content_for_name: options[:content_for_name] || :render_async
79
+ }
80
+ end
81
+
52
82
  def generate_container_id
53
83
  "render_async_#{SecureRandom.hex(5)}#{Time.now.to_i}"
54
84
  end
85
+
86
+ def replace_container(options)
87
+ return options[:replace_container] unless options[:replace_container].nil?
88
+
89
+ configuration.replace_container
90
+ end
91
+
92
+ def configuration
93
+ RenderAsync.configuration
94
+ end
55
95
  end
56
96
  end