I have this view:
<!-- Loads custom Stylesheet -->
<%= stylesheet_link_tag 'simulation' %>
<!-- Loads polling calls to update -->
<script type="text/javascript">
<%= render 'show.js.erb' %>
</script>
<!-- Render simulation -->
<%= render 'simulation' %>
<%= link_to 'Back', simulations_path %>
It contains these two partials:
_show.js.erb:
callUpdate = function(id) {
if (id) {
console.log("tic")
$.ajax({
type: "PUT",
url: "/simulations/" + id
});
} else {
console.log("Update error, no id passed.")
}
$('#sim_div').html("<%= j (render #simulation) %>");
setTimeout( function() {
callUpdate(id)
}, 5000);
};
setTimeout( function() {
callUpdate("<%= #simulation.id %>")
}, 5000);
_simulation.html.erb:
<div id="sim_div">
<h1><%= #simulation.identifier %></h1>
<h4 class="offset-col-sm-1">Dimensions: <%= #simulation.x_size %>x<%= #simulation.y_size %></h4>
<h4 class="offset-col-sm-1">Verdict: <%= #simulation.verdict %></h4>
<!-- REMOVE -->
<h1> <%= #simulation.dirty? %></h1>
<table class="table table-bordered">
<thead>
<tr>
</tr>
</thead>
<tbody>
<% #simulation.state.each_with_index do |row, y_index| %>
<tr>
<% row.each_with_index do |current, x_index| %>
<td class="text-center <%= 'dirty' if #simulation.dirty_points.include?([x_index,y_index]) %>"><%= current %></td>
<% end%>
</tr>
<% end %>
</tbody>
</table>
<br>
</div>
The javascript is calling my controllers update function, which sets dirty records on the update function.
simulation_controller#show:
def update
#simulation = Simulation.find(params[:id])
#simulation.next # <= THIS SETS DIRTY RECORDS ON THE OBJECT
#simulation.save
respond_to do |format|
format.js
format.html { redirect_to simulations_url }
end
end
My javascript code never gets the new updated record from after the update call, and I want it so that when their are no longer update calls to stop calling update and stop the polling. However it only can ever see the initial record which is not dirty. Only the _simulation partial seems to receive the new record (Even though it's getting re rendered by the javascript code so I'm not sure why the javascript can't see the updated record.)
How can I access properties of the updated record, not just the original in the javascript code? Thanks!
Your callUpdate javascript function likely is in fact being executed every 5 seconds, but it is rendering the same #simulation (the initial one) over and over without change. Your javascript partial is only being rendered once (initial view), and it is making endless ajax requests and endlessly replacing the html of some element with the same contents over and over.
Instead you should be updating the inner html of some element on every response of these ajax calls.
Take this line from your initial view:
$('#sim_div').html("<%= j (render #simulation) %>");
and move it to the response template of your update action (likely to be: update.js.erb)
Related
Rails & Javascript beginner here,
On a training project, I made flash messages disappear after few seconds using JQuery. A visitor would send AJAX request to add a product to his cart, then a flash partial 'Added to cart' appears and automatically fades out after few seconds.
# application.html.erb
<div id='flash' class='flex-column'>
<%= render partial: 'shared/flash' %>
</div>
# shared/_flash.html.erb
<% flash.each do |key, value| %>
<%= display_flash(key, value) %>
<%= javascript_pack_tag 'custom/flash' %>
# this works, but injects the script each times the partial is rendered
<% end %>
# helpers/application_helper.rb
def display_flash(key, value)
def display_flash(key, value)
div_class = [flash_class(key), 'text-center fade show alert-dismissible'].join(' ')
content_tag(:div, class: div_class) do
content_tag(:p, value) +
button_tag(class: 'ml-auto close', 'data-dismiss': 'alert', type: 'button') do
content_tag(:span, '×'.html_safe, 'aria-hidden': 'true') +
content_tag(:span, 'Close', class: 'sr-only')
end
end
end
end
// app/javascript/packs/custom/flash.js
function closeFlash(){
let lastAlert = $('#flash .alert:last')
function fadeFlash() {
lastAlert.animate( {opacity: 0}, 2000, function() {
$(this).hide('slow', function() {
$(this).remove()
});
});
};
setTimeout(fadeFlash, 2000)
};
closeFlash();
The issue with this is that it pollutes my DOM with unnecessary <script> tags:
This could be fixed, but is there a suitable way to execute one specific javascript function after rendering a (AJAX) partial ?
In my case, executing closeFlash() located in packs/custom/flash.js each time a partial is rendered.
Thanks for your help and your time
EDIT Solution:
From Amit Patel answer and this post
# app/views/shared/_flash.html.erb
<% flash.each do |key, value| %>
<%= display_flash(key, value) %>
<script>
$(function() {
closeFlash();
});
</script>
<% end %>
// app/javascript/packs/custom/flash.js
window.closeFlash = function() {
let lastAlert = $('#flash .alert:last')
function fadeFlash() {
lastAlert.animate( {opacity: 0}, 2000, function() {
$(this).hide('slow', function() {
$(this).remove()
});
});
};
setTimeout(fadeFlash, 2000)
};
It doesn't inject the whole function but (I believe) the minimal javascript code to call it.
Move <%= javascript_pack_tag 'custom/flash' %> to your layout or your application.js` so that it is available across the app.
modify your template like
application.html.erb
<div id='flash' class='flex-column'>
<%= render partial: 'shared/flash' %>
<script>
$(function(){
closeFlash();
});
</script>
</div>
A more recent approach would be to set a Stimulus controller.
what is the best way to implement a cancel button on an orders show page. The cancel button simply updates the order's status attributes to "cancelled" in a controller. I would like to carry over the order.id to the controller as each user has many orders.I am currently getting an undefined method 'id' for nil:Nilclass which makes me think the #order.id is not being passed into the hidden-field. Not sure what am doing is the best way to pass the order.id into the controller&welcome any ideas for a better solution
<div>
<% #orders.each do |order| %>
<%= order.id %>
<%= order.total %>
<%= order.user.name %>
//lots of boring stuff then at the bottom of the page
<%= form_tag guest_cancel_path, method: :post do |f| %>
<input type="hidden" name="order_id" value="<% order.id %>" >
<%= submit_tag "Cancel ",class: "cancel-button btn wide" %>
<% end %>
In my controller, I have:
def guest_cancel
#user = current_user
#order = Order.find(params[:order_id])
#order.update(status: 'cancelled')
redirect_to guest_requests_path, notice: " the order: #{#order} by user -> #{#user} has been cancelled, "
end
then in my routes:
post 'guest_cancel' => 'orders#guest_cancel'
It seems you missed to output it "<%= order.id %>"
you would need to add this the controller too: #order.update(status: 'cancalled')
If you have relation between listening and orders then you should write
<% listing.orders.each do |order| %>
so, orders instead of order.
I have a view such as:
<% for i in 1..5 %>
<p>
<div style="float: left; width: auto;">
<%= button_to(i.to_s, rate(#item.id, i, item_path), method: :get, remote: true) %>
</div>
</p>
<% end %>
This creates five buttons that link to this helper function:
module UsersHelper
def rate(itemid, rating, url)
#rating = Rating.new
#rating.userid = current_user.id
#rating.itemid = itemid
#rating.rating = rating
#rating.save
url
end
end
However, every time I load the page, the rate method is called five times for every rating from 1 to 5. Thus, it doesn't call the rate method when I click one of these buttons.
Why are all of these links to a helper method visited on each page load?
What I want is to call the rate method only when I click a button. How can I fix this for such a behavior?
Why all of these links to a helper method are visited on each page load?:
First thing's first, the view code is evaluated before rendering of the page on the client(browser). Hence, the loop and the ruby code within the loop is also being called/evaluated before the page is rendered on your browser. That's why rate method is being called for each value of i in your for loop.
Second, view helpers are for view layer logic(s), not for saving an active record object. Because each time you hit this page, you'll end up having ratings saved for that item from 1 to 5 in your db.
Solution:
You can create an another method in your ItemsController, let's call it rate:
def rate
#item = Item.find(params[:id])
#rating = #item.ratings.build(rating: params[:rating], userid: current_user.id)
respond_to do |format|
if #rating.save
format.js { }
else
format.js {render partial: 'errors', status: :unprocessable_entity }
end
end
end
In your routes.rb:
get '/items/:id/rating/:rating' => 'items#rate', as: :rate_item
In your view file:
<% for i in 1..5 %>
<p>
<div style="float: left; width: auto;">
<%= button_to(i.to_s, rate_item_path(#item, i)), method: :get, remote: true) %>
</div>
</p>
<% end %>
I like to update a table with the new values using ajax.
What is the idea. I have a cart, where the user can change the quantity of the product. I don't like to reload the whole page. I want to reload the table.
At the moment it works with reloading the whole page.
What I have done.
I have a form for input a quantity value. To connect the product with a cart (ManyToMany) I uses line_item. So every line_item represents a product in a cart.
This view of the cart
<div id="cart_table">
<%= render #cart %>
</div>
The render #cart
<table class="table table-hover table-condensed">
<tbody>
<%= render cart.line_items %>
</tbody>
</table>
Line_items with the update form:
<%= form_tag(line_item_path, method: "put", remote: true, class: "table_form") do %>
<%= hidden_field_tag("product_id", value = line_item.product.id) %>
<label>
<%= number_field_tag "quantity", value = line_item.quantity, in: 1...11 %>
<%= submit_tag "update", class: "btn btn-mini" %>
</label>
<% end %>
The controller of line_items:
def update
#cart = current_cart
#line_item = #cart.update_product(params[:product_id], params[:quantity])
respond_to do |format|
if #line_item.save
format.html { redirect_to current_cart, notice: 'Changed' }
format.js
else
format.html { render action: "new" }
end
end
end
Here is where I get stuck.
What I have to put in my update.js.rjs? Now I have this line
$('#cart_table').replaceWith("<%= escape_javascript(render #cart) %>");
But nothing happens and I get an error in the console:
send
jQuery.extend.ajax
$.rails.rails.ajax
$.rails.rails.handleRemote
(anonymous function)
jQuery.event.dispatch
elemData.handle
Would be nice to get some ideas to solve this little problem.
Thanks
Edit: It is better to use
$('#cart_table').html("<%= escape_javascript(render #cart) %>");
in the js file.
Try to use update.js.erb instead of update.js.rjs
I was trying out Rails again, this time the 3 version, but I got stuck while writing tests for an action that I only call remotely.
A concrete example:
Controller
class PeopleController < ApplicationController
def index
#person = Person.new
end
def create
#person = Person.new(params[:person])
#person.save
end
end
View (index.html.erb)
<div id="subscription">
<%= form_for(#person, :url => { :action => "create" }, :remote => true) do |f| %>
<%= f.text_field :email %>
<%= f.submit "Subscribe" %>
<% end %>
</div>
View (create.js.erb)
<% if #person.errors.full_messages.empty? %>
$("#subscription").prepend('<p class="notice confirmation">Thanks for your subscription =)</p>');
<% else %>
$("#subscription").prepend('<p class="notice error"><%= #person.errors.full_messages.last %></p>');
<% end %>
How can I test that remote form submission? I would just like to find out if the notice messages are being presented correctly. But if I try to do just
test "create adds a new person" do
assert_difference 'Person.count' do
post :create, :people => {:email => 'test#test.com'}
end
assert_response :success
end
It will say that the "create" action is missing a template.
How do you guys usually test remote calls?
Could you just use the 'xhr' function instead of the 'post' function? An example can be found at http://weblogs.java.net/blog/2008/01/04/testing-rails-applications, if you search for 'xhr'. But even then, I'm curious, even with a remote call, don't you need to return SOMETHING? Even just an OK header?