I need to generate a big report file in background. Here is a simple view to create a OrderReport object.
<%= simple_form_for order_report, remote: true do |f| %>
<%= f.input :start_date, as: :date, html5: true %>
<%= f.input :end_date, as: :date, html5: true %>
<%= f.submit "Generate report", id: "test" %>
<% end %>
And that what is going on in the controller:
def create
order_report = OrderReport.new(order_report_params)
order_report.user = current_user
order_report.save
OrderReportJob.new(order_report).delay.perform
render nothing: true
end
After user click a submit button this action creates a background process to generate report. I wrote endpoint to check the status of this background job. This JS is a onclick function to Submit buttom by id #test
$.ajax({
url: report_url,
success: function(report) {
if(report.status === 'progress') {
$("#spin").show();
$interval = setInterval(checkStatus, 3000);
}
}
});
This is a part of the JS script. It works good, but the final step to send the ID of created OrderReport to this js file. As you can see in the JS script I have a variable report_url - it's already hardcoded and looks like
var report_url = '/order_reports/1'
So the main idea is to catch the ID of created OrderReport, if it's possible, and use it in the JS script. How can I pass it correctly?
Update:
order_report.js
$(function () {
$('#test').click(function() {
var report_url = '/order_reports/39'
$.ajax({
url: report_url,
success: function(report) {
if(report.status === 'progress') {
$interval = setInterval(checkStatus, 3000);
}
}
});
function checkStatus() {
$.ajax({
url: report_url,
success: function(report) {
if(report.status === 'done') {
clearInterval($interval)
}
}
});
}
});
});
A more RESTful solution is to use meaningful response codes to tell the client what happened with the request:
def create
order_report = OrderReport.new(order_report_params)
order_report.user = current_user
respond_to do |format|
if order_report.save
OrderReportJob.new(order_report).delay.perform
format.json { head :created, location: order_report }
else
format.json { head :unprocessable_entity }
end
end
end
head :created, location: order_report returns a 201 - Created response with a location header that contains a url to the created resource.
This lets you listen for the Rails UJS ajax:success and ajax:error events:
<%= simple_form_for order_report, remote: true, html: { class: 'order_report_form', 'data-type' => 'json'} do |f| %>
<%= f.input :start_date, as: :date, html5: true %>
<%= f.input :end_date, as: :date, html5: true %>
<%= f.submit "Generate report", id: "test" %>
<% end %>
$(document).on('ajax:success', '.order_report_form', function(e, data, status, xhr){
function checkStatus(url) {
return $.getJSON(url).then(function(data) {
// some logic here to test if we have desired result
if (!desiredResult) {
// never use setInterval with ajax as it does not care
// if the previous request is done.
// instead use setTimeout with recursion
setTimeout(1000, function(){ checkStatus(url) });
} else {
// do something awesome
}
}
}
checkStatus(xhr.getResponseHeader('location'));
});
$(document).on('ajax:error', '.order_report_form', function(){
alert("Oops");
});
Related
I have a form that creates a relation and when the form is submitted, javascript code that addes the newly connected nodes and the relations connecting them to the cytoscape graph should be returned:
The form:
<%= form_for Relation.new, :url => url_for(:controller => 'relations', :action => 'add_dependency'), remote: true do |f| %>
<%= f.hidden_field :to_id, :value => #article.id %>
<%= f.hidden_field :graph, :value => 1 %>
<%= f.select :from_id, [], {}, {class: "select-article"} %>
<%= f.submit "Add a dependency of this article." %>
<% end %>
The controller code:
def add_dependency
#relation = Relation.find_or_create_by(relation_params)
#relation.user << current_user
respond_to do |format|
if #relation.save
elements = json_for_cytoscape(#relation.from.self_and_all_dependencies_of_depth_and_less(3))
format.json { render :show, status: :created, location: #relation }
format.js { render js: "ancestors.add( #{elements} ); console.log('Hello');" }
else
format.json { render json: #relation.errors, status: :unprocessable_entity }
end
end
end
I get this error (and no "Hello") in the javascript console:
Uncaught SyntaxError: Unexpected token :
at processResponse (rails-ujs.self-8944eaf3f9a2615ce7c830a810ed630e296633063af8bb7441d5702fbe3ea597.js?body=1:244)
at rails-ujs.self-8944eaf3f9a2615ce7c830a810ed630e296633063af8bb7441d5702fbe3ea597.js?body=1:173
at XMLHttpRequest.xhr.onreadystatechange (rails-ujs.self-8944eaf3f9a2615ce7c830a810ed630e296633063af8bb7441d5702fbe3ea597.js?body=1:228)
This is the response:
ancestors.add( {:edges=>[], :nodes=>[{:data=>{:id=>200, :title=>"Test Yourself: Area & arc length using calculus", :href=>"http://localhost:3000/articles/200", :rank=>0.000459770114943, :color=>"grey"}}]} ); console.log('Hello');
I solved the problem by adding to_json:
json_for_cytoscape(#relation.from.self_and_all_dependencies_of_depth_and_less(3)).to_json
That response is not valid javascript and the reason is pretty simple. When you cast a Ruby hash to a string the result is not valid JS:
irb(main):001:0> { :foo => 'bar'}.to_s
=> "{:foo=>\"bar\"}"
Instead you need to encode it as JSON.
irb(main):003:0> { :foo => 'bar'}.to_json
=> "{\"foo\":\"bar\"}"
Due to quoting issues is easier done if you actually create a view instead of rendering inline:
ancestors.add( <%= elements.to_json %> );
console.log('Hello');
I have implemented follow/unfollow functionality and would like to add AJAX call to it, but I am stuck.
My partial _follow_button.html.erb for follow/unfollow which is rendered on Users->index, looks like:
<% if current_user.id != user.id %>
<% if !current_user.following?(user) %>
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
<div><%= hidden_field_tag :followed_id, user.id %></div>
<span class="follow"><%= f.submit "Follow User", class: "btn btn-primary btn-sm" %></span>
<% end %>
<% else %>
<%= form_for(current_user.active_relationships.find_by(followed_id: user.id),
html: { method: :delete }, remote: true) do |f| %>
<span class="unfollow"><%= f.submit "Unfollow User", class: "btn btn-secondary btn-sm" %></span>
<% end %>
<% end %>
<% end %>
Then my controller for relationships looks like:
class RelationshipsController < ApplicationController
respond_to :js, :json, :html
def create
user = User.find(params[:followed_id])
#follow = current_user.follow(user)
end
def destroy
user = Relationship.find(params[:id]).followed
#unfollow = current_user.unfollow(user)
end
end
My view on user profile looks like:
<div class="col-5" style="margin-left: -5px;">
<%= render '/components/follow_button', :user => User.find_by_username(params[:id]) %>
</div>
My routes.rb have the following routes defined:
resources :users do
member do
get :following, :followers
end
end
resources :relationships, only: [:create, :destroy]
My Views folder structure has subfolders Users and Relationships. Both of them have separate controllers, and I have tried adding simple alert function 'alert("Works");' to the create.js.erb in both of those subfolders to try and match them with the controller, but none don't seem to work. This is my first Rails project, and I do not quite understand what the issue could be. Any suggestions?
Calling the partial follow/unfollow
<% if current_user.id != user.id %>
<%= render partial: 'follow_links', locals: { user: user }
<% end %>
Partial follow_links.
<% show_follow_link = current_user.following?(user) ? 'hidden' : '' %>
<% show_unfollow_link = current_user.following?(user) ? '' : 'hidden' %>
<!-- links to follow/unfollow have data-attributes that include the path to make the ajax post and the user to follow, that is used to find the link to show after the ajax call. You should use the path to the controller that will create or destroy the relationship -->
<%= link_to 'Follow', '#', { class: 'follow-user btn-success #{show_follow_link}', "data-url": follow_user_path(user.id), "data-followee": user.id } %>
<%= link_to 'Unfollow', '#', { class: 'unfollow-user btn-danger #{show_unfollow_link}', "data-url": unfollow_user_path(user.id), "data-followee": user.id } %>
Javascript for the partial. Ajax post to follow/unfollow
$('.follow-user').on("click",function() {
follow_unfollow($(this), "follow")
});
$('.unfollow-user').on("click",function() {
follow_unfollow($(this), "unfollow")
});
function follow_unfollow(target, what_to_do)
url = target.attr('data-url')
followee = target.attr('data-followee')
if (what_to_do == "follow") {
other_button = $('.unfollow-user[data-followee="'+followee+'"]')
} else {
other_button = $('.follow-user[data-followee="'+followee+'"]')
}
$.ajax( {
url: url,
type: 'post',
success: function() {
// Hide this link
target.addClass('hidden');
// Show the other link
other_button.removeClass('hidden');
},
error: function(ret) {
alert(ret.responseJSON.error);
}
});
};
Changes in your controller.
class RelationshipsController < ApplicationController
def create
user = User.find(params[:followed_id])
#follow = current_user.follow(user)
respond_to do |format|
if #follow.valid?
format.html
format.json: { render json: #follow }
return
else
format.html
format.json: { render json: { :error => 'Follow failed', :status_code :not_found } }
end
end
end
def destroy
user = Relationship.find(params[:id]).followed
#unfollow = current_user.unfollow(user)
respond_to do |format|
if #unfollow.valid?
format.html
format.json: { render json: #unfollow }
else
format.html
format.json: { render json: { :error => 'Unfollow failed', :status_code :not_found } }
end
end
end
end
An advice
An advice, also regarding your last question: I would recommend - instead of posting questions about debugging code on StackOverflow - create a good debugging environment for yourself.
Byebug or Binding pry is a good place to start, but before you can use those properly you need to understand the code you are using. I would recommend reading Working with Javascript in depth! - it really helped me getting the hang of it and understanding the dataflow of Rails and ajax.
This would, i think, break the unbreakable Stackoverflow-loop, that i myself were tied to for a long time:
loop do
puts "Try code"
sleep 1000
puts "Arrhh! an error!"
sleep 1000
puts "Posting on Stackoverflow"
sleep 1000
puts "Waiting for answer"
sleep 1000
end
I hope you figure it out!
I have a Rails app with a controller/view called "calls". Here is the basic controller action for index:
calls_controller.rb
def index
if params[:region].present?
#assigned = Call.where(region_id: params[:region][:area]).assigned_calls.until_end_of_day
#unassigned = Call.where(region_id: params[:region][:area]).unassigned_calls.until_end_of_day
else
#assigned = Call.assigned_calls.until_end_of_day
#unassigned = Call.unassigned_calls.until_end_of_day
end
end
Here are my views:
index.js.erb
$('#active').html("<%= escape_javascript render :partial => 'calls/assigned_calls', :locals => {:assigned_calls => #assigned} %>");
$('#inactive').html("<%= escape_javascript render :partial => 'calls/unassigned_calls', :locals => {:unassigned_calls => #unassigned} %>");
$(".select").select2({
placeholder: "Select One",
allowClear: true
});
index.html.erb
<div id="active">
<%= render "assigned_calls" %>
</div>
<div id="inactive">
<%= render "unassigned_calls" %>
</div>
<script>
$(document).ready(function() {
setInterval(function () {
$.ajax('calls/<%= params[:region][:area] %>');
} , 5000);
});
</script>
_assigned_calls.html.erb (view code omitted)
<%= form_tag calls_path, :method => 'get' do %>
<p>
<%= select_tag "region[area]", options_from_collection_for_select(Region.order(:area), :id, :area, selected: params[:region].try(:[], :area)), prompt: "Choose Region" %>
<%= submit_tag "Select", :name => nil, :class => 'btn' %>
So what's happening is on page load if I do not have the params of :region passed it sets the calls without being scoped by region. If region_id is present then it scopes calls where region_id is "1" or whatever the Region ID is that is passed from the submit_tag.
This works fine in the controller and view, however here's my problem. My index.html.erb I need to refresh the partials WITHOUT disturbing the params passed. So on setInterval I need to figure out how to reload the partials while persisting the params passed in the URL.
I tried to figure this out using a setInterval method but I'm not sure what I'm doing here 100%.
Can someone give me some advice on how to refresh the partials every 5 seconds while persisting the params so my instance variables persist through refresh?
If you need more context and/or code please let me know.
Update
Trying to rework the javascript based off an answer from a user and here's what I have.
<script>
$(document).ready(function() {
setInterval(function () {
$.ajax({
url: 'calls_path',
type: "GET",
data: { "region": '<%= #region.html_safe %>' }
}), 5000);
});
});
</script>
The page will load but when it tried to trigger in the chrome inspector I get:
calls?utf8=✓®ion[area]=3:2901 Uncaught SyntaxError: Unexpected token )
Maybe this is a JS syntax error or I'm not closing the function properly.
If I understood properly, you want to have your parameters somehow pipelined through AJAX call to your controller, back to your js.erb file where it refreshes the partials?
My advice is to set passed parameters as instance variables in your controller like this:
calls_controller.rb
def index
if params[:region].present?
#region = params[:region]
#assigned = Call.where(region_id: params[:region][:area]).assigned_calls.until_end_of_day
#unassigned = Call.where(region_id: params[:region][:area]).unassigned_calls.until_end_of_day
else
#assigned = Call.assigned_calls.until_end_of_day
#unassigned = Call.unassigned_calls.until_end_of_day
end
end
Now your #region instance variable will be available in your index.js.erb
where you can pass it to other partials you are trying to render.
index.js.erb
$('#active').html("<%= escape_javascript render :partial => 'calls/assigned_calls', :locals => { :assigned_calls => #assigned, :region => #region } %>");
$('#inactive').html("<%= escape_javascript render :partial => 'calls/unassigned_calls', :locals => { :unassigned_calls => #unassigned, :region => #region } %>");
_assigned_calls.html.erb
<%= form_tag calls_path, :method => 'get' do %>
<%= select_tag "region[area]", options_from_collection_for_select(Region.order(:area), :id, :area, selected: region.try(:[], :area)), prompt: "Choose Region" %>
<%= submit_tag "Select", :name => nil, :class => 'btn' %>
<% end %>
Also, I think that better practice in your index.html.erb script tag
is to do it like this:
<script>
$(document).ready(function() {
setInterval(function () {
$.ajax({
url: 'calls_path',
type: "GET",
data: { "region": '<%= #region.html_safe %>' }
});
}, 5000);
});
</script>
Please test this out if you're interested and get back to me :)
I am following section 4 (Server Side Concerns) to set up ajax on a page. I've copied the tutorial text completely (replacing the model names with my own) and it creates and saves my "Participants" record, but does not automatically refresh the ajax partial.
This is the error I get...which looks like it's referrring to my create.js.erb
ActionView::Template::Error ('nil' is not an ActiveModel-compatible object. It must implement :to_partial_path.):
1: $("<%= escape_javascript(render #participant) %>").appendTo("#participants");
2: // $('#participants').html("<%= j (render #participants) %>");
app/views/participants/create.js.erb:2:in `_app_views_participants_create_js_erb___1675277149181037111_70181034249880'
Here's my code
class ParticipantsController < ApplicationController
def new
#participant = Participant.new
#participants = #participants.recently_updated
end
def create
#participant = Participant.new(participant_params)
respond_to do |format|
if #participant.save
format.html { redirect_to #participant, notice: 'Helper Invited!' }
format.js {}
format.json { render json: #participant, status: :created, location: #participant }
else
format.html { render action: "new" }
format.json { render json: #participant.errors, status: :unprocessable_entity }
end
end
end
_form.html.erb
<ul id="participants">
<%= render #participants %>
</ul>
<%= form_for(#participant, remote: true) do |f| %>
<%= f.label :email %><br>
<%= f.email_field :email %>
<%= f.submit 'SUBMIT' %>
<script>
$(document).ready(function() {
return $("#new_participant").on("ajax:success", function(e, data, status, xhr) {
return $("#new_participant").append(xhr.responseText);
}).on("ajax:error", function(e, xhr, status, error) {
return $("#new_participant").append("<p>Oops. Please Try again.</p>");
});
});
</script>
<script>
$(function() {
return $("a[data-remote]").on("ajax:success", function(e, data, status, xhr) {
return alert("The helper has been removed and notified.");
});
});
</script>
_participant.html.erb
<li >
<%= participant.email %> <%= link_to participant, remote: true, method: :delete, data: { confirm: 'Are you sure?' } do %>REMOVE<% end %>
</li>
create.js.erb
$("<%= escape_javascript(render #participant) %>").appendTo("#participants");
destroy.js.erb
$('#participants').html("<%= j (render #participants) %>");
It's on line 2 of your create.js.erb file, it's the missing #participants not the #participant.
You've commented the line out in JS, but the ERB is still going to be processed by Rails, so it's still trying to do the render #participants
Update
For future... it's the last line of that error that's the key:
app/views/participants/create.js.erb:2
See the 2 at the end, that's telling you which line the error happened on, and so that's where you need to focus when looking for the problem.
I am so frustrated. I'm working with Stripe to create a form submission system for payments. Basically, the form makes an AJAX call to Stripe, which gives me a token on success, which I then use to resubmit the form, also through AJAX. If the form is successful, it redirects to a new page, if not, it populates the form with error messages without re-navigation. Here's my form:
<%= form_for([#issue, #issue_order]) do |f| %>
<% if #issue_order.errors.any? %>
<div class="error_messages">
<h2><%= pluralize(#issue_order.errors.count, "error") %> occurred. </h2>
<ul>
<% #issue_order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% f.hidden_field :issue_id %>
<%= f.hidden_field :stripe_card_token %>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="field">
<%= label_tag :card_number, "Credit Card Number " %>
<%= text_field_tag :card_number, nil, name: nil %>
</div>
<div class="field">
<%= label_tag :card_code, "Security Code on Card (CVV) " %>
<%= text_field_tag :card_code, nil, name: nil %>
</div>
<div class="field">
<%= label_tag :card_month, "Card Expiration " %>
<%= select_month nil, {add_month_numbers_true: true}, {name: nil, id: "card_month"} %>
<%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year + 15}, {name: nil, id: "card_year"} %>
</div>
<div id="stripe_error"></div>
<div class="actions"><%= f.submit "Purchase Issue", id: "submit_issue_order" %></div>
<% end %>
<div class="errors"></div>
Here is the javascript that handles the form and sets up the stripe information:
var issueOrder;
$(function() {
Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'));
issueOrder.setupForm();
});
var issueOrder = {
setupForm: function() {
$('#new_issue_order').submit(function(e) {
e.preventDefault();
$('#submit_issue_order').attr('disabled', true);
issueOrder.processCard();
return false;
});
},
processCard: function() {
var card;
card = {
number: $('#card_number').val(),
cvc: $('#card_code').val(),
expMonth: $('#card_month').val(),
expYear: $('#card_year').val()
};
Stripe.createToken(card, issueOrder.handleStripeResponse)
},
handleStripeResponse: function(status, response) {
if (status == 200) {
$('#issue_order_stripe_card_token').val(response.id);
// $('#new_issue_order')[0].submit();
$.ajax({
type: "POST",
url: $('#new_issue_order').attr('action'),
data: { "issue_order": {
"stripe_card_token": $('#issue_order_stripe_card_token').val(),
"email": $('issue_order_email').val(),
},
"issue_id": $('#issue_order_issue_id').val()
},
dataType: "script"
}, issueOrder.processOrder);
}
else {
$('#stripe_error').text(response.error.message);
$('input[type=submit]').attr('disabled', false)
}
}
And here is my controller:
def create
charge = Stripe::Charge.create(
:amount => 400,
:currency => "usd",
:card => params['issue_order']['stripe_card_token']
)
if charge['paid'] == true
#issue_order = IssueOrder.new(email: params['issue_order']['email'], issue_id: params['issue_id'])
if #issue_order.save
#pdf_token = #issue_order.pdf_token
PdfMailer.pdf_email(params['issue_order']['email'], #issue_order).deliver
else·
flash[:error] = []
flash[:error].push("Your card was charged, but sadly we were unable to create·
a record in the database. Please contact us for your copy of the issue.")
respond_to do |format|
format.js
end
end
else
# run checks for errors and return error messages
flash[:error] = []
flash[:error].push("There was an error in processing your payment.")
render :json => {success: false}
end
end
Typical stripe setup stuff. Works fine when the stripe order is successfully processed. Well, it did before I hand-rolled the AJAX call, I'm assuming it'd be fine if I threw a respond_to |format| in the success case in the controller that redirected to the success page. However, for the error cases, my controller renders create.js.erb, which looks like this:
console.log('yo');
$('.errors').empty();
errors = xhr.getResponseHeader('X-Flash-Error').split(',');
<% flash[:error].each do |error| %>
$('.errors').append($('<p>' + <%= error %> + '</p>'));
<% end %>
setTimeout(function() {
$('.errors').empty();
}, 3500);
The controller clearly reaches the file and renders it, as evidenced by the logs:
Started POST "/issues/1/issue_orders" for 127.0.0.1 at 2013-11-12 23:33:17 -0500
Processing by IssueOrdersController#create as JS
Parameters: {"issue_order"=>{"stripe_card_token"=>"tok_102vmu2pSkyWUgPAToj334Oa"}, "issue_id"=>"1"}
(0.4ms) BEGIN
(0.4ms) ROLLBACK
Rendered issue_orders/create.js.erb (0.1ms)
["Your card was charged, but sadly we were unable to create \n a record in the database. Please contact us for your copy of the issue."]
Completed 200 OK in 1363ms (Views: 3.9ms | ActiveRecord: 0.8ms)
But NOTHING happens on my page, including the console log! If I don't hand roll the AJAX and use remote: true, it gets even worse -- it recognizes my submit() call as HTML and doesn't know what to do with the format, rendering an Unknown Format error.
Help!