Rails 4: How to update index page with AJAX - javascript

I can't believe I haven't found other questions to answer this but I've searched high and low and can't find anything that really answers my question.
Basically, I have an Expenses page in which I want to display all Expenses in a given month in a table. I have a month and year select and right now I have it working by adding month and year parameters to the url and going to that href with a little javascript. I would like to avoid a page refresh however and just update the table when the select box value is changed. As I understand it, because I'm not using a form, I can't use :remote => true and thus have to use AJAX. My research has brought me to set things up as the following.
This is my JQuery with an AJAX call:
$(document).ready(function() {
$('#monthSelect').change(function() {
// alert("yay");
var m = $(this).val();
var y = $("#yearSelect").val();
$.ajax({
type: "GET",
url: "/expenses",
data: { month : m, year : y } ,
success: function(data) {
},
dataType: "json"
});
});
});
#monthSelect and #yearSelect are the ids of my two select boxes. This part seems to work. When I change the value of the month select box, it sends the GET request to "/expenses" with the correct month and year in the params.
/expenses naturally routes to my index action. Here is the part where I'm confused.
def index
#expenses = # Code that gets the expenses that correspond to the month and year in the params.
# This part works. I've tested it and #expenses are all the records I want
respond_to do |format|
format.html
format.json { render json: #expenses, status: :ok } #?????
end
end
So I guess my primary question is how the json part works, and if I should even be using json in the AJAX. Tutorials and videos I've looked at all seem to imply that getting the response in json is the way you're supposed to do it, but I'm not sure what "rendering json" even means. My understanding is that passing #expenses, it basically will render what #expenses.to_json returns, which is just a bunch of json, key-value pairs. How do I take that and make a table?
A friend of mine that has done plenty of AJAX but no Ruby on Rails said that I can just write the HTML in the AJAX success function
...
success: function(data) {
$("#expenses-table").html("HTML of my Table");
}
But this just doesn't seem like the Rails way, to put a bunch of Javascript code that just puts HTML into a string. And then what does the render: json do if I'm just putting the HTML in the AJAX? I did see an answer here on Stack Overflow that had
success: function(data) {
$("#expenses-table").html(data);
}
which would be nice, since data does have all the json I want. But obviously that doesn't just work like that.
So if I could get some clarification on this whole mess, if I'm even approaching it right with JSON, or if I should just write the HTML in the AJAX, that would be much appreciated.
EDIT 1:
Here's my current table code:
<table id="expenses">
<thead>
<tr>
<th>Description</th>
<th>Amount</th>
<th>User</th>
<th>Date</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #expenses.each do |expense| %>
<tr id="expenses-row">
<td><%= expense.description %></td>
<td><%= number_to_currency expense.amount %></td>
<td><%= expense.user.name %></td>
<td><%= expense.date.strftime("%m/%Y") %></td>
<td><%= link_to 'Show', expense %></td>
<% if expense.user == #user %>
<td><%= link_to 'Edit', edit_expense_path(expense) %></td>
<td><%= link_to 'Delete', expense, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% else %>
<td></td>
<td></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
Basically just the standard scaffold table. Most of the changes were just css. Here's what the json looks like for one record
[{"id":8,"description":"This is a test","amount":"35.0","user_id":1,"date":"2014-10-01","created_at":"2014-10-03T07:07:53.412Z","updated_at":"2014-10-03T07:07:53.412Z","receipt_file_name":null,"receipt_content_type":null,"receipt_file_size":null,"receipt_updated_at":null}]
I guess I'd rather do the code in ERB/ruby as I'm more immediately familiar with it. Though it almost feels a little pointless to have the json since #expenses is what I already use in my current table code. However, wouldn't render :file "expenses_table.html.erb" refresh the whole page? Or how would it know where to render just that table and replace what was previously there? It almost sounds easier to respond to format.js and make an index.js.erb because I know I could replace the html with my partial. But I haven't tried that as all the examples I've seen respond to format.json. If it sounds like I'm just confused and don't know what's going on it's because I am. I just need some clarification. Thanks for your help!

if I should even be using json in the AJAX.
You can use any type of String you want. The problem is parsing the string on the javascript side to extract each piece of data. For instance, you could respond with a String such as:
"exp1=10, exp2=20, exp3=30"
Then your javascript code can split the String on ", ", then the "=" sign, then use the pieces to create an object, then you can use the object to refer to the data. On the other hand, if the data is sent as a JSON string, all you have to do in the javascript is:
var obj = JSON.parse(data);
...and obj will be something similar to a ruby Hash, where you can look up keys to get values.
So I guess my primary question is how the json part works...My
understanding is that passing #expenses, it basically will render what
#expenses.to_json returns, which is just a bunch of json, key-value
pairs. How do I take that and make a table?
That's correct. You make the table using your programming skills.
A friend of mine that has done plenty of AJAX but no Ruby on Rails
said that I can just write the HTML in the AJAX success function
Sure, but the trick is still to programmatically take the data in the json and insert it into an html table. If you want to go that route, you could return a complete table as the response, so that you can use ERB/Nokogiri/ruby on the server side to insert the data in the table. Then you would write something like:
#expenses = ....
render :file "/some/dir/expenses_table.html.erb"
Another approach might be to give each <td> in the table an id attribute that is equal to a key in your json data. Then you can use javascript to loop over each key in the json data, look up the <td> with that id, then replace the entry with the value in the json data corresponding to the key.
If you want more concrete suggestions, you'll have to post a small example of your table's html as well as what #expenses.to_json looks like.
By the way, the jQuery ajax() function has too many features, so all of the common ajax requests have shortcut functions, where the relevant options are filled in for you, e.g. getJSON(), where you just specify the url, data, and the success function. In this case, that doesn't save you much work, but maybe the function name is more descriptive than 'ajax'.
Response to comment questions:
I guess my confusion then is which javascript is this? Is this the
original AJAX call?
Yes. That is the only javascript you need to code.
Should that be in a js.erb file then for me to
call some ruby code?
No. You would use a js.erb file if you want to create some js using ruby (that is what erb is for), AND you want to return js code as the response. We've established that you don't want to return javascript as the response. However, using rails to setup ajax is very confusing, so I may not be understanding the gist of your questions. It's much simpler not to use rails to generate ajax javascript.
As far as I understand things, if you put remote: true in your rails html helper when you create an html form, then when the form is submitted, a request is sent to your rails app with an Accept header of text/javascript. That means if your action has a respond_to block with a format.js line, that line will execute, and you can send some javascript back to the browser, e.g. a .js.erb file that contains code that performs an ajax request to get your expense data. Your browser will immediately execute the returned js, which will send an ajax request back to the server asking for the expense data. Note that the rails way is inefficient: the browser has to send two requests to the server to accomplish one ajax request: one request is sent to the server to get the js code that will make an ajax request for the expense data, then the browser executes the ajax code which sends another request to the server to get the expense data.
Or can I call a javascript file in the respond
to?
It looks to me like you want to replace the existing table with a whole new table, i.e. you are not changing two or three <td>'s in the existing table, which should makes things very easy. In fact, as far as I can tell, you can use the same erb code that you used to create the existing table to create the new table. However, your index action's response to an ajax request should only return the table--not the whole page. That suggests you should put the code that creates the table into a separate template, a partial:
views/shared/_table.html.erb:
<table id="expenses">
<thead><th>Description</th><th>Amount</th><th>Date</th></thead>
<% #expenses.each do |expense| %>
<tr>
<!-- I used an abbreviated Expense model: -->
<td><%= expense.description %></td>
<td><%= number_to_currency expense.amount %></td>
<td><%= expense.date %></td>
</tr>
<% end %>
</table>
Then you include the partial in views/expenses/index.html.erb:
<h1>Expenses#index</h1>
<p>Find me in app/views/expenses/index.html.erb</p>
<div>
<select id='expense_day'>
<option value="all">All</option>
<option value="2014-10-5">10/5/14</option>
<option value="2014-10-6">10/6/14</option>
</select>
</div>
<%= render "shared/table" %> <!-- HERE -->
By default, render() will look for a partial in the app/views directory called shared/_table.html.erb.
Those two files combined (inserted into the application layout) make up the index.html.erb view. Because the table is now a separate template, just the table can be rendered in response to an ajax request:
class ExpensesController < ApplicationController
def index
respond_to do |format|
if request.xhr? #Is this an ajax request?
target_date_str = params['target_date']
if target_date_str == "all"
#expenses = Expense.all
else
#expenses = Expense.where(date: Date.strptime(target_date_str, "%Y-%m-%d"))
end
#Render just the table for the ajax request:
format.html { render partial: "shared/table" }
else #then not an ajax request
#expenses = Expense.all
format.html #By default renders index.html.erb
end
end
end
end
respond_to() looks at the Accept header in the request and based on that chooses a matching format:
format.html
format.json
format.xml
etc.
I examined the request sent by jQuery's .get() ajax function, and it sends a request with an Accept header of text/html as the highest priority, so format.html will be chosen in the respond_to block. Edit: I originally wrote the index action thinking there would be a format.json line for the ajax request, and a format.html line for a non ajax request. But because the index action now returns html in both cases, the respond_to() block isn't needed:
class ExpensesController < ApplicationController
def index
if request.xhr? #if an ajax request...
target_date_str = params['target_date']
if target_date_str == "all"
#expenses = Expense.all
else
#expenses = Expense.where(date: Date.strptime(target_date_str, "%Y-%m-%d"))
end
render partial: "shared/table" #Overrides rails default operation which renders index.html.erb
else #then not an ajax request
#expenses = Expense.all #rails defaults to rendering index.html.erb
end
end
Here is the javascript I used:
<script>
$( document ).ready(function() {
$("#expense_day").on("change", function() {
var selected_date = $(this).val();
$.get(
"/expenses",
{ target_date: selected_date },
function(table) {
$("#expenses").replaceWith(table)
}
);
});
});
</script>
<h1>Expenses#index</h1>
<p>Find me in app/views/expenses/index.html.erb</p>
<div>
<select id='expense_day'>
<option value="all">All</option>
<option value="2014-10-5">10/5/14</option>
<option value="2014-10-6">10/6/14</option>
</select>
</div>
<%= render partial: "shared/table", expenses: #expenses %>
I put the javascript in the index.html.erb view, which should make it clear that the javascript is part of the html page. However, you can remove the javascript from index.html.erb and put it in another file--but eventually the js has to find its way back into the index.html.erb file. Here are some links that discuss alternate locations for the javascript:
Why is rails not rendering the .js.erb file?
Best way to add page specific javascript in a Rails 3 app?
Rails has something called the asset pipeline, which is a method for cramming all the javascript and css into as few lines as possible by removing all the whitespace. Because the files are smaller, your browser can load them faster. To take advantage of that minifying, you have to put your assets, i.e. your javascript and css files, into certain directories. That is really not something you need to worry about because your javascript code is not thousands of pages long.

Related

Rails & AJAX, is there a reason you shouldn't render html view directly in controller action for ajax to process?

The classic way to work with Rails & Ajax is always something that looks like this:
// JS - let's assume this submits to dummies#create
$(form).submit()
# Dummies Controller
def create
#dummy = Dummy.new(dummy_params)
respond_to do |format|
format.js
end
end
# /views/dummies/create.js.erb
$("page").append("<%= escape_javascript(render partial: 'dummy_view' ) %>");
# /views/dummies/_dummy_view.html
<h1><%= #dummy.name %></h1>
I've always been curious, because the above seems to create a random create.js.erb file with very little meat... is there a reason (e.g., it's terrible convention, or terribly insecure or whatever), why you should NOT instead just render the view directly back to ajax?
// JS - basically takes responsibilites of create.js and puts it into the always
$.ajax(...).always(function(xhr, status){
$("page").append($(xhr['responseText']))
// responseText contains the partial rendered by the controller action
})
# Dummies Controller
def create
#dummy = Dummy.new(dummy_params)
render partial: 'dummy_view'
end
# /views/dummies/_dummy_view.html
# unchanged
<h1><%= #dummy.name %></h1>
NOTE above is pseudo-code, apologies for minor errors. The conceptual idea & question remain unchanged, though.
The create.js.erb is not random, is the view for the action with the expected format.
Generally, you may not have a view so simple (You may have different selectors other than "page", you may have some extra js code to be executed after or before the append), a js view/script is the general solution for an ajax request to give the response full control over what to do.
You could have what you want, but it will just work for your particular case when the selector is always "page" and you only want to append html to that element. Nothing prevents you from doing that (though you might want to use a custom ajax request and not rails' one since it sets js format by default and executes the response's script).
The convention is that a rails' remote request renders a js script, you can move out of the convention if you want. You'll lose a lot of flexibility with your approach as is (like... what if the create action fails an you need to display errors?).

Rails update element based on AJAX request?

I've been reading a lot about Rails and AJAX and 5.1 Unobtrusive javascript. It explains a lot about responding to Rails version of AJAX calls with a .js file for example.
However what im wanting to do isn't serving up an entire .js file, it's simply updating an element after a <% link_to %> POST request. From my understanding setting remote: true submits it as a AJAX request.
Essentially I have a "Post" which a user can like via a linked Like button. This sends a POST request to the "Post" controller which updates a post to liked and adds a like to the post.
Unfortunately to see the effects of the post being liked (Which is simply that the link changes color as well as the font-awesome icon) you need to refresh the page. I basically want it to update without needing refresh.
I "think" based off what i've read I need to make a respond do and respond via .js to the request with a .js file in the view I want to update (for instance if the controller action is called "like", maybe a like.js.erb file in the view im updating?). But I don't want to serve an entire new page..or would this simply just run the .js?
Then I could do something like $('i.fa-icon#id').style.color = "blue" or something? (Im assuming I can send data from the controller to the .js.erb file?). Not sure the best way to do this, don't rails elements a lot of times have some sort of data-attribute or something (Im still a beginner at this).
Your description is quite correct!
Opposed to the other answer, you don't even need a event listener but as you said you want to have a respond_to in the controller.
So starting from the html:
# post/index.html.erb
<div id="like-button">
<%= button_to "Like this post", post_path(#post), remote: true %>
</div>
Note, that when you use a button_to helper it'll be a POST request by default.
If you click it, it'll go to the controller#update, which you want to change to this:
#posts_controller.rb
...
def update
#post.save
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js # <-- will render `app/views/posts/update.js.erb`
end
end
Note: the format.html is rendered when JS is disabled.
Now in the scenario that JS is enabled, it executes the app/views/posts/update.js.erb file. It can look like this:
const likeButton = document.getElementById('like-button');
likeButton.innerHTML = '<%= j render "posts/liked-link", post: #post %>';
What is the last line doing? Of course, you can change the style directly with the JavaScript, but you can also render a new partial - and this you will create in a new html file:
# app/views/posts/liked_link.html.erb
<div id="like-button ">
<p>"You liked this post!" </p>
</div>
I just changed the link/button to ap now, but of course you can do whatever you want.
Hope that makes sense :)
Not sure if I understand the question, but if you want to update like button:
What you want to do is to add an event listener to the button, and when clicked it makes a POST request to whatever route handles the likes(with the correct parameters) and your controller should respond with the like object (or whatever in the database gets stored). Have your post request on success method to grab the like button and change it to whatever you want it to look like
$(“#like-btn”).click(function(){
Rails.ajax({
url: "/some/url/to/like/controller",
type: "post",
data: [your post data],
success: function(data) { $(`#${ data[“btn-name”] }`).attr(“color”, “blue”; }
})
}
You can stick this script right in the bottom of the html page
You don’t have to do it exactly like this, just giving you an idea of how to set up the pattern of having JavaScript and Ajax handle the post request and updating of the frontend instead of using html buttons

Rails and AJAX remote: true what else is required?

I'm confused about remote:true in Rails forms, I thought some Javascript was required to make it asynchronous but this just seems to break my page.
Here is a really simple index.html.haml that includes a partial to show all appointments:
%h1 Calander
%h2 AppointmentsController
%h3 Make a new appointment
= form_for #appointment, remote: true do |f|
= f.text_field :title
= f.text_field :appt_time
= f.submit 'Make appointment'
#appointments
=render 'appointments'
Here is the previously mentioned partial:
-#appointments.each do |a|
%h3= a.title
%p= a.appt_time
Controller methods for index and create:
def index
#appointments = Appointment.order('appt_time ASC')
#appointment = Appointment.new
end
def create
#appointmet = Appointment.create(appointment_params)
redirect_to :root
end
Now this works fine. I can add a new appointment, hit submit and the new appointment shows up without the page refreshing, I think because I have included remote: true. So do I need to add anything else to handle the request? Am I violating best practices by not including something to handle this request and relying entirely on remote: true?
Nothing more required unless you want some callback after ajax call. You did not break any conventions. You can read this document to get ride of confusion.
Let's take a step back.
Web applications can respond to different request formats. Rails has built-in format handling.
So a request might ask for index via HTML, which response with an HTML file. It might also request index via JSON, XML, PDF or even JavaScript.
Whenever you add remote: true you are telling your form make a POST request via JS instead of HTML.
In your views you will have a bunch of HTML.ERB files. These views are request responses.
So to handle a JS request to index, you will need a app/views/appointements/index.js file.
This will be sent as the response to the request and the browser will know what to do with a JS response.
In index.js you can write JS that will be executed once the response is received.
You can also load partials into the page.
For example:
# app/views/appointements/index.js
$('#appointements').html('<%= j render "appointements" %>')
Which will render the partial content as a JavaScript string for the response.
http://guides.rubyonrails.org/working_with_javascript_in_rails.html

Include a Coffeescript file with ERB in a view

This is giving me a major headache...
So I have an app which requires a sidebar that lists various information to do with a user's player. One section of this sidebar is a friends list. Now, when Player A sends a friend request to Player B, the request should be automatically logged in B's sidebar, and I intend to use WebSockets to do this.
Here is my cp.js.coffe.erb file (there's only a few snippets of ERB at the moment; there will be loads more and I rather get this working first):
$ ->
$("#cp").accordion()
if `"WebSocket" in window`
socket = new WebSocket("ws://localhost:8080")
socket.onopen = =>
console.log("Connection Open")
init = {
sender: "cp"
action: "init"
user: <%= #user.id %>
token: <%= cookies["remember_token"] %>
}
socket.send(init.to_json)
socket.onerror = (e)=>
console.log(e)
socket.onclose = =>
console.log("Closed")
socket.onmessage = (m)=>
console.log("Recieved: #{m.data}")
msg = m.data.JSON.parse
switch msg.action
when "ret_init"
when "friend_udt"
refreshFriend()
refreshFriend() ->
html = "<%= j render 'layouts/friends' %>"
$('#friends').empty()
$('#friends').add(html)
Theoretically, the code itself works fine, the problem being that Rails doesn't let you use ERB in the assets pipeline, and so this file has to sit in app/views/layouts.the file cannot access the variables declared within a controller or use the render method (or most other ERB methods).
Here's the thing: I can't include said file in my application.html.erb file, and I looked into requesting the file with AJAX, but from my understanding that will immediately execute the Javascript once and once only, and I need the methods in this to be constantly available to update the sidebar.
Is there any way of including this file so that it works with the ERB and the CoffeScript so that it would be continuously avaliable to the page? Am I misunderstanding the whole AJAX requesting method?
Thanks #nzifnab for your help with the JS. Now my friends partial looks like this:
<ul id="friendlist">
<% if Relation.find_by(owner: #user.id, type: "freq") != nil %>
<% Relation.find_by(owner: #user.id, type: "freq").each do |r| %>
<li class="friend-request-cp"><%= link_to "/#{User.find(r.character).name}" %></li>
<% end %>
<% end %>
<% if Relation.find_by(owner: #user.id, type: "friend") != nil %>
<% Relation.find_by(owner: #user.id, type: "friend").each do |r| %>
<li class="friend-cp"><%= link_to "/#{User.find(r.character).name}" %></li>
<% end %>
<% end %>
</ul>
I need to apply two different styles to each item, hence why I'm using the ERB here. This works fine, as it's loaded when the page is first navigated to, but my code was supposed to re-render that partial every time a notification comes through of any new interactions. It would then repopulate the list using the data from the database again. Is there a more efficient way of doing this? Can I still do this with the hamlcoffeeassets gem you showed me?
Slight tangent ensues:
By the way, I'm using Ruby 2.0.0-p247 and Rails 4 on Windows 7. I felt the need to include that because of some major compatibility issues with gems that are much different from Ubuntu. I had to move from Ubuntu to Windows because updating from 13.04 to 13.10 broke everything Ruby Gem on that OS. I don't have tome to find a fix: I literally have only four days to get this app built.
You can kinda use erb in the asset pipeline, but you have to remember that it only gets rendered ONCE, EVER, and not once for every user and so even if there was an #user variable (which there won't be), it would never change. You can use erb in your coffee file for things like route paths and environment variables, but not for things like user-specific config and dynamic changes to the JS. It's bad practice anyway.
What you should really do is use a javascript library to read cookies instead of trying to do it with rails (This will give you access to some of the things you appear to be trying to do). And when you need more dynamic behavior you should render data-attributes or other values into the html DOM itself and use the javascript to read that.
Take a look at this cookie library: https://github.com/carhartl/jquery-cookie
There's many others to look at via a quick google search.
socket.onopen = =>
console.log("Connection Open")
init = {
sender: "cp"
action: "init"
user: $.cookie('user_id')
token: $.cookie('remember_token')
}
There are a couple of ways to render new markup for your view using JS. One way is to use js templates. I'm a big fan of the hamlcoffeeassets library here: https://github.com/netzpirat/haml_coffee_assets Although it uses haml for the view, and not ERB. There are ERB variants as well.
You would add some markup to app/assets/templates/friend.jst.hamlc like so:
%p This is my friend markup #{#friend.name}
And then you can render it from your JS like this:
$('#friends').append(JST['templates/friend'](friend: {name: 'Bob'}))
Which will append the markup from your template with the values you've passed interpolated in. In my example you'd end up with this markup inside your #friends container:
<p>This is my friend markup Bob</p>
Alternatively you can render the partial you want via rails into your JSON response as just a string, and then insert that into your document...
So your JS might look something like this:
socket.onmessage = (m)=>
console.log("Recieved: #{m.data}")
msg = m.data.JSON.parse
switch msg.action
when "ret_init"
when "friend_udt"
refreshFriend(msg.friendHTML)
refreshFriend(html) ->
$('#friends').html(html)
UPDATE
In reference to your ERB question... First of all your partial is incredibly inefficient making similar calls to the database four times every time you render it. haml_coffee_assets is for use with the haml markup language (which I prefer over ERB), if you want ERB then use eco instead: https://github.com/sstephenson/eco
If you want to render this in the JS, then you need to send this "friend relation" data as JSON through the notification data response, you do not have access to active record OR any controller methods or instance variables when rendering javascript partials - they don't hit back to the server, they only use what is accessible by your javascript at the time.
This should really go to app/assets/javascripts/cp.js.coffee.erb, you can use erb in the asset pipeline just fine (see here) Make sure you are spelling the coffee extension right, though!
Doing this, you should be able to call this via ajax without problems, the path would be /assets/cp.js.
try this gem: 'coffeebeans'
name your coffee file as "some_file.html.erb"
<%= coffeescript_tag_do %>
# your coffee script here ...
<% end %>
in another erb file:
<%= render file: '.../some_file' %>

Rails Javascript parsing using array from Controller

I am doing Ajax Call from View, then controller is fetching data from DB into an Array, now this array value I want to pass to JavaScript, so that I can update data in a table with different id's
Controller code:
def AjaxView
#var1 = Var.find(:all,:conditions => { :varname=> "one" },:select=> (params[:col]))
respond_to do |format|
format.js
end
end
AjaxView.js.erb code:
$(document).ready(function() {
$("#test").text("valuetoupdate");
});
Now when I run this code it successfully update "valuetoupdate" at id =test in view page.
Instead of this I want to update all values one by one from array #var1.
I searched more and realized that array #var1 generated in Controller will automatically get transferred to AjaxView.js.erb file. Now I have to iterate over all values, but this code doesn't work in JavaScript file:
<% for var in #var1 %>
$("#3").text(var);
<% end %>
it gives blank response
Thanks Guys,
I am able to resolve my issue, in following manner,
I moved away from for loop implementation, instead start using if else condition,
<% if #var.to_s == 'valuetoupdate' %>
{ }
<% elsif #var.to_s == 'valuetoupdate1' %>
{}
And it worked fine for me.

Categories

Resources