render_async 2.1.0 → 2.1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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