I've spent almost my entire day trying to figure out how to solve this dilemma but unfortunately a majority of the solutions I've found are related to an outdated Rails version that still allowed "render" to be used in assets.
Here's what I'm trying to do:
My view finds each "Trip" entry and displays each as a thumbnail on the page. When the user clicks on each thumbnail, I would like the additional details (and also associations, each trip has a has_many: weeks) to be rendered in the Div below those thumbnails (replacing the previous content).
I can't seem to get Ajax to work and after several hours of attempting finally learned that "render" can't be used in assets. I would sincerely appreciate any help and along with potential solutions if someone could provide a possible reference guide for Ajax with Rails 4 that would be fantastic because I can't seem to find one.
Here's my code:
View - index.html.erb
<div>
<ul class="thumbnails col-md-offset-2">
<% Trip.find_each do |trip| %>
<li class="col-md-3" style="list-style-type:none;">
<%= link_to image_tag("http://placehold.it/350x350", :border => 0), :class => 'thumbnail', :url=>'/trips/selected_trip/params', :remote => true %>
<h3><%= trip.name %></h3>
</li>
<% end %>
</ul>
<div id="selected_trip">
</div>
</div>
Controller - trips.controller.rb
class TripsController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
def index
#trips = Trip.all
authorize Trip
end
def new
#trip = Trip.new
authorize Trip
end
def create
#trip = Trip.new(trip_params)
authorize Trip
if #trip.save
flash[:notice] = "New trip has been created."
redirect_to #trip
else
#Fill me in
end
end
def edit
#trip = Trip.find(params[:id])
authorize Trip
end
def update
#trip = Trip.find(params[:id])
authorize Trip
#trip.update(trip_params)
flash[:notice] = "Trip has been updated."
redirect_to #trip
end
def show
#trip = Trip.find(params[:id])
authorize Trip
end
def destroy
#trip = Trip.find(params[:id])
authorize Trip
#trip.destroy
flash[:notice] = "Trip has been deleted."
redirect_to trips_path
end
def selected_trip
#trip = Trip.find(params[:id])
#trip.name
respond_to do |format|
format.js
end
end
end
private
def trip_params
params.require(:trip).permit(:name, :description, :status)
end
end
Javascript - trips.js.erb (I know this method doesn't work anymore with render not being available in assets)
$('#selected_trip').html("<%= escape_javascript(render :partial => 'selected_trip', :content_type => 'text/html'%>")
Partial - _selected_trip.html.erb
<p>Success!</p> <!-- Just for testing, will replace with actual content -->
Thanks,
Nate
Edit 11:10PM (it works)-
I've changed my controller to:
def selected_trip
#trip = Trip.find(params[:id])
authorize Trip
render :partial => 'selected_trip', :content_type => 'text/html'
end
and my view to:
<div>
<ul class="thumbnails col-md-offset-2">
<% Trip.find_each do |trip| %>
<li class="col-md-3" style="list-style-type:none;" data-tripID="<%= trip.id %>">
<%= link_to image_tag("http://placehold.it/350x350", :border => 0), selected_trip_trip_path(trip), :class => 'thumbnail', :remote => true %>
<h3><%= trip.name %></h3>
</li>
<% end %>
</ul>
<div id="selected_trip">
</div>
</div>
</div>
<script>
$('a.thumbnail').on('ajax:success', function(evt, data) {
var target = $('#selected_trip');
$(target).html(data);
});
</script>
If you want to avoid rendering from the assets, you can try doing it this way.
I am assuming you know where you need to put the listener in order to catch the AJAX call, and also you can figure out, where you want to place the results when the AJAX comes back with a success status. Then, you want to do something like this:
$('whatever_container_they_can_click').on('ajax:success', function(evt, data) {
var target = $('#selected_trip'); // Find where is the destination
$(target).html(data);
});
Change your controller action to:
def selected_trip
#trip = Trip.find(params[:id])
render :partial => 'selected_trip', :content_type => 'text/html'
end
Related
I have an issue with a Rails AJAX app which is confusing me, even though it seems very simple! I am dealing with class Order in a simple point of sale rails app. The request is being made as the order will be deleted on page refresh (but I am getting no refresh of #orders) as I thought I am specifying in destroy.js.erb.
orders/index.html
<div id="orders">
<%= render 'orders/index' %>
</div>
<%= link_to 'New Order', new_order_path, remote: true %>
<div id="order-form" style="display:none;"></div>
orders/_index.html
<% if #orders.any? %>
<% #orders.each do |order| %>
<%= link_to "Show #{order.id}", order_path(order), class: "something" %>
<%= link_to "Delete #{order.id}", order_path(order), method: :delete, class: "something" %>
<%= link_to "Delete order with ajax", order_path(order), remote: true, method: :delete %>
<% end %>
<% else %>
<p>No orders yet</p>
<% end %>
destroy.js.erb
//When deleting order on order index - render orders again.
$('#orders').html("<%= j (render 'orders/index') %>");
and the relevant actions from orders_controller.rb
class OrdersController < ApplicationController
respond_to :html, :js
def index
#orders = Order.paginate(page: params[:page])
if params[:search]
#orders = Order.search(params[:search]).order("created_at DESC")
else
#orders = Order.all.order('created_at DESC')
end
end
def destroy
#order = Order.find(params[:id])
if #order.destroy
flash[:notices] = ["Order was successfully deleted"]
redirect_to orders_path
else
flash[:notices] = ["Order could not be deleted"]
render order_path(#order)
end
end
I suspect the issue is in my orders_controller destroy or index action, but I am a little unclear on a number of the ways of working with AJAX in Rails.
Link to repo - https://github.com/benhawker/point-of-sale-rails
Might be because after your destroy you're redirecting to the index path
I am getting no refresh of #orders
Your JS is likely not firing, you'll need the following:
def destroy
#order = Order.find params[:id]
respond_to do |format|
if #order.destroy
format.js
format.html { redirect_to orders_path, notice: "Order was successfully deleted" }
else
format.js
format.html { render order_path(#order), notice: "Order could not be deleted" }
end
end
end
This will fire app/views/orders/destroy.js.erb, which seems okay in your OP.
Try Updating your destroy action to
#order = Order.find(params[:id])
#order.destroy
#orders=Order.all
remove all the redirects
will work.
How do i make it so that it does not refresh the page? instead it will just update the count numbers? Any help would be appreciated!
Stories controller:
def like
like = Like.create(like: params[:like], user: current_user, story: #story)
if like.valid?
flash[:success] = "Your selection was succesful"
redirect_to :back
else
flash[:danger] = "You can only like/dislike a story once"
redirect_to :back
end
end
index.html.erb:
<div class="pull-right">
<%= link_to like_story_path(story, like: true), :method => :put, :remote => true do %>
<i class="glyphicon glyphicon-thumbs-up"></i><%= story.thumbs_up_total %>
<% end %>
<%= link_to like_story_path(story, like: false), :method => :put, :remote => true do %>
<i class="glyphicon glyphicon-thumbs-down"></i><%= story.thumbs_down_total %>
<% end %>
</div>
Story.rb model:
def thumbs_up_total
self.likes.where(like: true).size
end
def thumbs_down_total
self.likes.where(like: false).size
end
The controller should respond_to the js "format", as described in Working with JavaScript in Rails.
Instead of redirect_to :back, respond with a meaningful status code, for example 201 for success (when a new resource has been created) or 400 for a bad request.
See RFC 2616, section 10 - Status Code Definitions.
Then, in your JS, handle the ajax:success event. This is also described in Working with JavaScript in Rails.
Let me show you an example:
First change your StoriesController as follow:
def like
#like = Like.create(like: params[:like], user: current_user, story: #story)
if #like.valid?
respond_to do|format|
format.js
end
else
respond_to do|format|
format.js {render status: 403, js: "alert('You can only like/dislike a story once'"}
end
end
end
And create a file called like.js.erb within your stories view directory with following content.
<% if #like.like %>
var $element = $(".glyphicon.glyphicon-thumbs-up");
<% else %>
var $element = $(".glyphicon.glyphicon-thumbs-down");
<% end %>
var oldCount = parseIn($element.text());
$element.text(oldCount + 1);
With the examples aside, you should at least Rails Guide in order to be able to use ajax effectively in rails. It's not that difficult.
I faced the same problem with this guy
I change rjs to js.erb just like him. And we all use <%= button_to 'Add to Cart',line_items_path(:product_id => product) ,:remote=>true %> to send an AJAX request to the controller. format.js to fine and execute create.js.erb. But the cart did not add anything.
log result :
Rendered line_items/_line_item.html.erb (4.3ms)
Rendered carts/_cart.html.erb (8.0ms)
Rendered line_items/create.js.erb (8.8ms)
That's the index.html.erb we send the AJAX request
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% #products.each do |product| %>
<div class="entry">
<%= link_to image_tag(product.image_url), line_items_path(:product_id => product), html_options = {:method => :post} %>
<h3><%= product.title %></h3>
<%=sanitize product.description %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price,:precision=>3) %></span>
<%= button_to 'Add to Cart',line_items_path(:product_id => product) ,:remote=>true %>
</div>
</div>
<% end %>
That's the line_items controller function to handle the request
# POST /line_items
# POST /line_items.json
def create
# for exercise only
session[:counter] = nil
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.add_product(product.id)
respond_to do |format|
if #line_item.save
format.html { redirect_to store_index_path }
format.js
format.json { render json: #line_item, status: :created, location: #line_item }
else
format.html { render action: "new" }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
create.js.erb
$('#cart').html("<%= escape_javascript(render(#cart)) %>");
I fixed the problem.
Thanks to the great article which tell me the ability of firebug to see the source of response from AJAX request.
And JSLint helps me checkout the javascript syntax.
And finally I would like to thanks the Firebug which is such a great tool.
The problem is that the javascript is not being executed if there is any syntax error.
In my problem:
I should use single-quoted instead of double-qouted to wrap the render results. The render results comes out with many HTML with "", and "" which wrap them will cause syntax error in javascript. (double-qouted in double-qouated is not allowed)
So I simply change $('#cart').html("<%= escape_javascript(render(#cart)) %>"); to
$('#cart').html('<%= escape_javascript(render(#cart))%>');
I hope this answer will help the others who also suffer from this nightmare staff.
Help me increase the question rate if this is possible :)
Let's use j() helper method: $('#cart').html("<%=j render(#cart) %>");
I currently have a comment model that posts under a micropost and both are displayed on the same page. The issue is that both are displayed on the same page and both are paginated and I am trying to go for the facebook approach to microposting. Here is the issue below:
The links for both pagination turns into this href="/users/2?page=2" rather than href="/users/2/micropost?page=2" or href="/users/2/comment?page=2". I am unsure how to go about solving this problem. Here are some of my code. All suggestions are much appreciated!
Micropost Render HTML
<table class="microposts">
<% if microposts.any? %>
<%= render microposts %>
<%= will_paginate microposts, :page_links => false %>
<% else %>
<div class="EmptyContainer"><span class='Empty'>Add a thread!</span></div>
<% end %>
</table>
Comment Section HTML
<div id='CommentContainer-<%= micropost.id%>' class='CommentContainer Condensed2'>
<div class='Comment'>
<%= render :partial => "comments/form", :locals => { :micropost => micropost } %>
</div>
<div id='comments'>
<% comments = micropost.comments.paginate(:per_page => 5, :page => params[:page]) %>
<%= render comments %>
<%= will_paginate comments, :class =>"pagination" %>
</div>
</div>
User Controller for the Show Page
def show
#user = User.find(params[:id])
#comment = Comment.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#comments = #micropost.comments.paginate(:page => params[:page], :per_page => 5)
#microposts = #user.microposts.order('created_at DESC').paginate(:per_page => 10, :page => params[:page])
respond_to do |format|
format.html
format.js
end
end
Problem lies within will_paginate way of creating urls for each page (it doesn't have anything to do with jQuery).
By design, will_paginate try its best to guess what's the base url for the page user is on (internally it's using controller/action to do that). That base url is then combined with any extra params passed to will_paginate helper using :params and succesive page numbers.
For now (will_paginate 3.0.3), in order to overwrite this default behavior, you need to write your custom LinkRenderer class. Below there's example of such class - it makes use of new, extra option :base_link_url that can be passed to will_paginate view helper. Passed string is then used as a base when creating pagination links. If :base_link_url option is not passed, it will fallback to default behavior.
Put following class somewhere rails can find it on load (/lib for example, provided you've added /lib to your autoload paths in application.rb):
# custom_link_renderer.rb
class CustomLinkRenderer < WillPaginate::ActionView::LinkRenderer
def prepare(collection, options, template)
#base_link_url = options.delete :base_link_url
#base_link_url_has_qs = #base_link_url.index('?') != nil if #base_link_url
super
end
protected
def url(page)
if #base_link_url.blank?
super
else
#base_url_params ||= begin
merge_optional_params(default_url_params)
end
url_params = #base_url_params.dup
add_current_page_param(url_params, page)
query_s = []
url_params.each_pair {|key,val| query_s.push("#{key}=#{val}")}
if query_s.size > 0
#base_link_url+(#base_link_url_has_qs ? '&' : '?')+query_s.join('&')
else
#base_link_url
end
end
end
end
Usage:
# in your view
will_paginate collection, :renderer => CustomLinkRenderer, :base_link_url => '/anything/you/want'
And now back to your case. By this time you probably see the solution - you can have two will_paginate widgets on one page with different base urls by passing different :base_link_url options for those two.
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?