Reputation: 163
I have a basic form that, when submitted, triggers a Create request to an external API. On submit, a stage value with progress bar appears instead of the Submit button. The creation process takes time on the external API, so I had to run a couple of Show requests each second using while status == 'inactive'
and stop when the Show request response has status: 'active'
.
Every Show request response also contains a 'stage'
attribute which shows the stage of the creation process (Connecting, Creating, Fetching, Finishing etc.). With every request, the current stage is stored in the @stage variable, which can be accessed with ApiRequest.stage
. Every API related stuff is in a service file.
My coffeescript that changes the Submit button with the stage+progress bar:
$('#new_connection').on 'submit', ->
button = $('#submit_button')
progress = $('.loading')
button.fadeOut 500
setTimeout (->
if button.is(':hidden')
progress.fadeIn 500
return
), 509
return
And the stage+progress bar partial:
.loading
%p.center-align#stage <!-- This should display the stage in real time -->
.progress <!-- This is a materializecss progress bar -->
.indeterminate
How can I update the text inside #stage
with ApiRequest.status
every time the value changes in real-time staying on the same page, from the moment the form is submitted until the end (it will redirect to create.html when all the stuff in the background is done anyway)?
Upvotes: 0
Views: 258
Reputation: 163
Ok so all you do is create a method in the controller that reads the variable: @stage = your method here
.
Write a respond_to do |format|
with format.json { render json: { stage: @stage } }
.
Now you have to create a route for this custom controller method: get '/your/link', to: 'controller#custom_method', as: 'stage'
.
All that is left is to trigger this method through Ajax:
$.get({
url: "your/link.json",
success: function(data) {
$('#your_paragraph_id').text(data['stage]);
},
});
You can put this Ajax call in a setInterval function.
Upvotes: 0
Reputation: 5977
To display the status of your operation in real time or at least close to real time, you have to either
show
endpoint every second or two and update the status label in the success callbackPolling is like when you are driving a car and one of your passengers keeps asking "are we there yet?" every second. It is not very efficient, especially if the operation takes a long time, since a separate HTTP request is issued every n seconds, only to come back with a "still in progress" response. But it is easy to implement, so if the show
action is fast and the operation itself finishes reasonably quickly, then it can be a good compromise.
The alternative is to have the server notify you when a status change happens. To do this, you will need to have a publisher-subscriber infrastructure in place. If you are using Rails 5, Action Cable is probably your best option as it is built right into Rails. You can find a detailed comparison of the different available technologies here: In what situations would AJAX long/short polling be preferred over HTML5 WebSockets?
If you are using polling, you can update the status label in the success
callback of the regular AJAX calls to show
. If show
renders HTML only, then you will have to parse out the new status from the AJAX response data:
<!-- example output of show.html.erb -->
<!-- ... -->
<div id="current-status"><%= @status %></div>
<!-- ... -->
Example in plain JavaScript for parsing the page in the AJAX response and getting the value of the #current-status
<div>
:
setTimeout(function() {
$.get({
url: "<%= show_page_path %>",
success: function(data) {
var response = $("<div></div>").
append($.parseHTML(data, null, true));
$("#current-status").html(
$("#current-status", response)
);
},
});
}, 1000);
Of course, if you add a JSON endpoint for the show
action that includes just the status:
# in the controller
respond_to do |format|
format.html
format.json do
render json: { status: @status }
end
end
Then you don't have to do any HTML parsing:
setTimeout(function() {
$.get({
url: "<%= show_page_path(format: :json) %>",
success: function(data) {
$("#current-status").text(data.status);
},
});
}, 1000);
The examples are untested, so they might not work perfectly without a few modifications.
Upvotes: 1