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.
Related
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.
I'm trying to understand AJAX requests in Rails. I have a form that I currently submit using remote: true. I want to respond with an HTML redirect if the request is successful, and run an error message with Javascript if it is unsuccessful. However, no matter what the outcome is, the request seems to expect a .html as the return.
respond_to do |format|
if conversation
format.html { redirect_to(conversation_path(conversation)) }
else
format.js
end
end
This is called after I save the conversation call on AJAX. On a successful save, the path HTML is correctly sent back, but is not rendered on the client. But on an unsuccessful save, it expects the .html and throws an error. How do I accept .js as a response? My goal is to just pop up an error if the call is unsuccessful and redirect_to on a successful call.
Edit: My form_for:
<%= form_for :conversation, url: :conversations, remote: true, html: { class: "conversation-form" } do |f| %>
Here's a suggested alternative to your end-goal - in the controller, drop the format.html entry in your respond_to block. Also, set conversation to an instance variable that the view template can access:
class YourController < ActionController::Base
def your_action
# awesome code doing stuff with a conversation object goes here
#conversation = conversation
respond_to do |format|
format.js # your_action.js.erb
end
end
end
Then, put the redirect logic in your javascript view template (for the above example: app/views/.../your_action.js.erb):
<% if #conversation.errors.any? # or whatever condition you want to show a popup for %>
// put your javascript popup code here
alert('Errors happened!');
<% else %>
// this is how you do a redirect using javascript:
window.location.href = "<%= conversation_path( #conversation ) %>";
<% end %>
Hope this helps!
I'm working on a very simple RoR4 forum-like application. This is the forums_controller.rb actions for creating a new forum:
def create
#new_forum = Forum.new(forum_params)
if #new_forum.save
respond_to do |format|
format.html { redirect_to forums_path }
format.js
end
else
respond_to do |format|
format.html { render 'new' }
format.js { render partial: 'form_errors' }
end
end
end
Nothing magic there. The create js.erb looks like this:
<%= add_gritter("This is a notification just for you!") %>
<%= broadcast "/forums/new" do %>
$('#forum_form').remove();
$('#new_forum_link').show();
$('#forums_table').append('<%= j render #new_forum %>')
<% end %>
Everything is actually working fine. The broadcast block call you can see uses Faye to broadcast the javascript managing the changes in the interface.
My concern is, whatever is outside of the block in the js.erb file (i.e. that add_gritter call) is totally ignored. I'm sure it's not a gritter problem because changing that line for a simple alert('hi'); does nothing. However, if the call is moved inside the block it gets executed (with the annoying effect that every client gets the notification, alert or whatever). I am really out of ideas regarding this, and would appreciate any help.
<%= broadcast "/forums/new" do %>
is spitting the result of the execution as javascript (that HTTOK) which makes it go meh. So it should be
<% broadcast "/forums/new" do %>
Note the lack of the =.
I'm a newbie using AJAX and now I'm trying to work on a group of select boxes so that when the user selects a value from the first, the second updates its values. I'm trying to use AJAX, JQuery and ROR, but so far, I can't fetch the value from the first box and pass it to the controller in order to filter the results of the second.
This is my code for index.html.erb
<%= form_tag(posts_path, remote: true, :method => 'get', class: "forma") do %>
<%= select_tag("box", options_from_collection_for_select(#posts, :content, :content), :prompt => "Select") %>
<p>
<%= select_tag("nbox", options_from_collection_for_select(#listposts, :content, :id), :prompt => "Select") %>
<p>
<%= submit_tag "Show", class: "btn btn-info" %>
<% end %>
This is my index.js.erb
$(document).ready(function() {
$(".btn").on("click", function() {
$('.table').fadeToggle();
})
$('#box').change(function(){
$.ajax({
type: 'POST',
url: '/jess/rails/dummy/app/controllers/posts_controller',
data: {'filter' : $(this).val() },
success: function(data){
}
})
$('#nbox').fadeIn();
$('.btn').fadeIn();
})
})
This is my controller
class PostsController < ApplicationController
respond_to :html, :js
def new
#post = Post.new
end
def create
#posts = Post.all
#post = Post.new(post_params)
if #post.save
flash.now[:notice] = "YEAH"
respond_to do |format|
format.html {redirect_to #post.content}
format.js
end
else
render "/"
end
end
def show
#post = Post.find(params[:id])
end
def index
#posts = Post.all
#listposts = Post.where('content LIKE ?', params[:box])
end
def next
#listposts = Post.where('content LIKE ?', params[:box])
end
private
def post_params
params.fetch(:post, {}).permit(:content)
end
end
I think I'm doing it wrong with the AJAX part in the js.erb but I've changed it so many times I don't know anymore. Your help would be so very much appreciated!!
It looks like there are a couple of small problems.
First, it looks like the URL you are using may be wrong. The URL for the AJAX request would be the same format as the URL if you were to view that page in your browser.
With what you provided I can't tell exactly what it should be, but I can give you an example. Assuming you access your index page by just "http://example.com/index" (or something similar), you would also call the function you want as something based on it's route. If the route was "update", then it would be something like "http://example.com/update".
Basically, don't use the folder path but use the web path instead.
Second, you don't do anything your success callback in your AJAX request. With AJAX, you send and receive info from the server, then you have to use JavaScript to update whatever you need updated manually. So, you'll need to have your update action return some information which you can then use to change the state of your second set of boxes.
Trying to figure out Ajax search in a Rails 3 app, following this post, which itself borrows from a Railscast. Currently, when I submit, nothing happens, and if I go into the Chrome console, I see this error:
GET http://localhost:3000/search?utf8=%E2%9C%93&search=&_=1361491523102 404 (Not Found)
posts.js line 5 seems to be culpable, but that just seems to be the jQuery call, so I'm not sure what I'm doing wrong.
My relevant code:
posts.js.coffee
jQuery ->
# Ajax search on submit
$('.search_form').submit( ->
$.get(this.action, $(this).serialize(), null, 'script')
false
)
posts_controller.rb
def show
#search = Post.search(params[:search])
respond_to do |format|
format.html # show.html.erb
format.json { redirect_to #post }
end
end
def search
#search = Post.search(params[:search])
render json: { results: #search }
end
post.rb
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
else
scoped
end
end
show.html.erb
<%= form_tag search_path, method: "get", class: "search_form form-inline",
style: "display:inline-block;" do %>
<%= text_field_tag :search, params[:search], placeholder: "Search:", id: "search_field" %>
<%= submit_tag("Search", name: nil, class: "btn search", remote: true) %>
<% end %>
# Testing for #search stops it from pulling a "no method '.first' for nil NilClass" error.
<div id="list"><%= render partial: 'list', locals: { search: #search } if #search%></div>
list.html.erb
<ul>
<% #search.each do |p| %>
<li>(<%= p.created_at.strftime("%b %d, %Y") %>)</span></li>
<% end %>
</ul>
Any idea what's going wrong here?
EDIT -- Adding relevant lines of routes file:
resources :posts
# ...
match '/search', to: 'posts#search'
I already had that '/search' route there. Is that right -- in which case, is the problem somewhere else? Does the rest of the code look goodish?
EDIT -- added search method to PostsController, per Damien's rec. New things of interest are a) the way I call the partial, the new search action, and that fact that then I tried to replace the contents of the show method in Post.rb with "Post.all", it still didn't show anything. No errors thrown, though.
"No search action! Didn't realize I needed one. What should be in it?
When I tried an earlier version of this without AJAX, I had a search
action that was basically a replica of show (+ #search etc), with all
the same variables, otherwise it wouldn't work, because it rendered
the show page. Is that the right idea? It seemed very unDRY"
Using AJAX, there is no need to render the entire show page. Just return the filtered results in desired format:
def search
#search = Post.search(params[:search])
render json: { results: #search }
end
Add format based returns if necessary.