The right way to do remote (ajax) calls in Ruby on Rails - javascript

I'm making a Single Page Application with Ruby on Rails (it's my first ruby project ever so I'm definitely missing a lot of stuff yet).
So I have a side menu with some links and the right part of the page is supposed to hold a container which is meant to be filled with some content of partial pages.
The typical menu link I have now looks this way:
<%= link_to t('my-groups'), :controller => 'dashboard', :action => 'mygroups', :remote => true %>
I have a dashboard controller, here's the simplified version of it
class DashboardController < ApplicationController
def mygroups
respond_to do |format|
format.js
end
end
I have a dashboard template with the container div in it
<div class="right_col" role="main">
<h2>This is the default content of center page</h2>
</div>
And here's the routes.rb path for it:
get 'dashboard/mygroups' => 'dashboard#mygroups'
I also have one partial page alogside with my dashboard template and it's called _mygroups.html.erb and a javascript file mygroups.js.erb which is called as my controller action
look at the screenshot of the structure
The contents of this js.erb file are:
$('.right_col').html("<%= escape_javascript(render(:partial => 'mygroups')) %>");
It all works and the partial contents appear inside the container on link click just fine.
But there are still 2 problems I couldn't google the answer for
The questions part:
1) It works with Ajax call but if I simply put this http://localhost:3000/dashboard/mygroups to my browser's navigation line and hit enter, it will give me this error
ActionController::UnknownFormat in DashboardController#mygroups
ActionController::UnknownFormat
Extracted source (around line #70):
def mygroups
respond_to do |format|
format.js end end
How can I avoid this and just redirect to index in this case?
I understand that ajax uses POST, but I tried to use post instead of get in routes.rb for this action, and it didn't work at all
2) What if I have a lot of actions for different partial pages, do I have to create a new js.erb file for each action? Can't it be done in some simplier way with just one file?
3) Is it possible to not specify controller and action on this link explicitly?
<%= link_to t('my-groups'), :controller => 'dashboard', :action => 'mygroups', :remote => true %>
I mean since it's supposed to be a POST ajax request, how come I need to display the url like this http://localhost:3000/dashboard/mygroups to a user?

Add format.html in controller like:
class DashboardController < ApplicationController
def mygroups
if request.xhr?
respond_to do |format|
format.js
end
else
redirect_to root_url
end
end
you can add url in link_to tag like:
<%= link_to t('my-groups'), '/dashboard/mygroups', :remote => true %>

Answers to you questions
When you hit the URL in browser, it sends vanilla HTTP get request(non-ajax) which your controller action is not configured to handle it. You need to add format.html and template named groups.html.erb where generally you will list all the groups, I guess.
respond_to do |format|
format.html
format.js
end
Ideally you have to create separate file for each action but if you can take something common out of different action then you can move common template code to a partial and render it either in a separate template having something special or from the controller action directly.
Yes. The rails way is to use routes helper. Run rake routes to list all available routes in your app and find relevant helpers.
I would strongly suggest to read the rails guide to understand how everything works.

Related

ActionController::UnknownFormat with respond_to js for AJAX

I am searching for the internet about this topic, but I don't get how AJAX works with rails, I already check the documentation and I just simply don't understand it.
What I know is that AJAX is asynchronous and it just only takes to put this code in the view for make the request: "remote:true", the big deal that I don't get it is about this code:
respond_to :js
Apparently, it tells the controller that it would respond to Javascript and you have to make a file for whatever you wanna do with JS, my structure of my project is this one:
View
New.html.erb:
<p>Imagen de portada</p>
<%= simple_form_for #entries, remote: true do |f| %>
<% f.file_field 'input-image' %>
<% end %>
<div id="image-entry"></div>
View route:
views
|
-->admins
|
-->entries
|-->new.html.erb
|-->new.js.erb
Controller
entries_controller.rb
module Admins
class EntriesController < ApplicationController
before_action :authenticate_admin!
def index
render 'index'
end
def new
#entries=''
render 'new'
respond_to :js
end
end
end
Controller route:
controllers
|
-->admins
|
-->entries_controller.erb
JS
new.js.erb
console.log('funciona');
$('#input-image').click(function(){
$('body').css('background-color', 'red');
});
JS route:
views
|
-->admins
|
-->entries
|-->new.html.erb
|-->new.js.erb
Error
My error in this code is the following:
ActionController::UnknownFormat
So, I have questions about this error and what is the correct name of the file in JS for get the respond_to work correctly.
Answer by Irfan Fadilah on Facebook:
Your request is not processed by "new" method in EntriesController. The default form method is POST, so Rails will looking for "create" method in you EntriesController.
You can read RESTful routing in Rails Guide for more details.
In order to make your AJAX request to works. Add "create" method in EntriesController and create.js.erb (just write alert("Hello"); or something to test it) in views/entries.

Convert working Ruby on Rails form to AJAX

I have a working Ruby on Rails form that currently posts my results and redirects the user to another view and posts the results. The flow is:
views/tools/index.html.erb -> views/tools/ping.html.erb
Since I have it working now, I'd like to convert it to AJAX and keep the user on the views/tools/index.html.erb view, getting rid of the redirect to enhance the user experience. However, I'm unsure of how to proceed based on the way that my Tools controller is currently setup, and my incredibly lacking knowledge of AJAX.
So, here's what I currently have:
views/tools/index.html.erb (added 'remote: true' to form)
<h1> Tools </h1>
<h3> Ping </h3>
<%= form_tag ping_tool_path(1), method: "post", remote: true do %>
<%= text_field_tag :ip, params[:ip] %>
<%= submit_tag "Ping", name: nil %>
<% end %>
<!-- this is where I'd like to put the results via AJAX -->
<div id="output"></div>
controllers/tools_controller.rb
class ToolsController < ApplicationController
def index
end
def ping
ping_host(params[:ip])
save_host(params[:ip])
# Adds based on recommendations
respond_to do |format|
format.html { redirect_to tools_path }
format.js
end
end
protected
def ping_host(host)
f = IO.popen("ping -c 3 #{host}")
#output = f.readlines
tool_type = "ping"
tool = Tool.find_by(tool_type: tool_type)
tool.increment(:tool_hit_count, by = 1)
tool.save
#results = "<pre>#{#output.join}</pre>".html_safe
end
def save_host(host)
host = Host.find_or_create_by(host_ip: host)
host.increment(:host_hitcount, by = 1)
host.save
end
end
views/tools/ping.html.erb
<%= #results %>
views/tools/ping.js.erb (New file based on suggestion)
$("#output").html("<%= #results %>");
routes.rb
Rails.application.routes.draw do
root 'tools#index'
resources :tools do
member do
post 'ping'
end
end
end
This is what I see on the Network tab in Google Chrome after submitting the form:
So, what I know at this point is that I'll need to add remote: true to my form in views/tools/index.html.erb, and this is where I get lost.
It seems that I have an issue ATM with the fact that I've abstracted the form to use my ping method in the Tools controller, whereas all of the tutorials (and railscasts) I've gone through are doing AJAX on CRUD methods and a given model, not something like what I've build here so far. Please help me understand AJAX!
You're on the right track, now you need to modify the def ping action with a respond_to block.
def ping
ping_host(params[:ip])
save_host(params[:ip])
respond_to do |format|
format.html { redirect_to tools_path } ## if you still want to have html
format.js
end
end
and create a file called view/tools/ping.js.erb where you have javascript that will be run and returned asynchronously
$("#output").html("<%= j #results %>");
The <%= %> block will be evaluated first and replaced with the output of that ruby code. Then this will be inserted into the #output div.

JS actions after Ajax response in Rails

There is the following select tag for ajax request:
= form_for owner, url: change_status_admin_owner_path(owner), remote: true do |f|
.row
= f.select(:status_id, options_from_collection_for_select(Status.all, :id, :description,
owner[:status_id]), {}, { data: { remote: true, url: change_status_admin_owner_path(owner), method: :patch } })
Action in controller:
def change_status
Owner.find(params[:id]).update!(status: Status.find(params[:owner])
end
And change_status.js.erb:
$("html").remove();
It's JS for example, not really task, but it doesn't work - instead of doing my JS code I get:
<div class='content'>
<div class='container'>
$("html").remove();
</div>
</div>
I.e. Rails renders my JS code as just text! What's wrong in my code? Thanks!
If you want an action to set the response as JS, try adding a respond_to block like this:
def change_status
Owner.find(params[:id]).update!(status: Status.find(params[:owner])
respond_to do |format|
format.js
end
end
This will look for a change_status.js.erb file in your views folder.
Further to my comment & manu29.d's answer, you'll be best looking at using the respond_to block:
def change_status
...
respond_to do |format|
format.js #-> looks for change_status.js.erb
end
end
--
However, this would not explain why your response is being rendered as HTML (and not JS)
I would surmise that since your application is not processing the response as the text/javascript mime type, Rails will be looking for any format of view called change_status in your folder (I.E it can be .html.erb or .js.erb)
As it's found one called .js.erb, I would guess that it's being processed as static HTML (as erb is just a preprocessor). To fix this, the respond_to block will basically set the mime-type, allowing your browser to digest the JS

Comprehensive ajax page for Rails

I have a Controller named ProductsController. In that controller are several actions such as socks and towels. I also have views of socks and towels. The view pages of socks and towels have the exact same form inside it. I'm going to use ajax in both files for the forms. Since the form is the exact same in both views, I see it pointless to create multiple ajax js files for the actions to call when ajax is called. How can I have both actions socks and towels call the same js.erb file instead of socks.js.erb and towels.js.erb respectively?
My controller and the action socks.
def socks
#socks = Socks.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #socks }
#ok, I get the line below is needed for ajax. Is there something I could add for
#the action not to call socks.js.erb and instead a different file, say ajax.js.erb
format.js
end
end
I think you're looking for render here. Using it you can render a template/view of another action. See the doc: Layouts and Rendering.
render 'products/show'
render :template => 'products/show'
render "/u/apps/warehouse_app/current/app/views/products/show"
as Sergio rightly said, you can do exactly that using the render command. Basically, in both socks and towels actions, you can render a common ajax.js.erb file when the respond_to is for JS.
I am adding this answer however because of your comment. It seems that your socks and towels methods as well as the forms and associated responses are very similar. If that is the case, why not use a single action for the same? Basically, you can use a URL parameter to specify the nature of product in the ProductsController actions.
So you would have something like:
def ProductsController
def index
if params[:nature] == "socks"
#socks = Sock.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #socks }
format.js
end
elsif params[:nature] == "towels"
#towels = Towel.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #towels }
format.js
end
end
end
end
Now you would need 2 view files index.html.erb and index.js.erb and there you can check the params value again and display the relevant collection.
This is not an ideal solution, but more a quick fix.
Failing that, you can also define a new action method in the ProductsController and have both forms point to that.

Can I redirect_to a javascript request to another javascript action?

I have a comments controller with index and create actions among others. Both those actions respond to html and js format.
Whenever I got create request via ajax, I would like to add new comment and then redirect to index.js, so the comments on screen are updated without reloading the page.
This sort of thing works flawlesly in Chrome. But whenever I try this in Firefox or IE, it turns out, that the redirect from create.js lands in index.html...
Even when i force the redirect to be js:
redirect_to polymorphic_path([#commentable, :comments]), :format => 'js'
It land up in the format.html in Firefox and IE.
Any idea what might be happening here?
There are various issues with the way browsers handle 302 requests differently. Some lose request types, others lose request method (an example ticket: http://trac.tools.ietf.org/wg/httpbis/trac/ticket/160).
I would suggest that rather than redirecting to a new URL when using JS, you simply render the same action. So something like this:
class CommentsController < ApplicationController
def index
setup_for_index
respond_to :html, :js
end
def create
# Creation stuff...
respond_to do |format|
format.html {redirect_to :action => :index}
format.js do
setup_for_index
render :action => :index
end
end
end
private
def setup_for_index
#comments = ...
end
end

Categories

Resources