I have a following code that is part of the _form.html.erb code. Basically I have a form in which I have a observe_field function where on change, it will set fields' values without refreshing the page. Following is my html code:
<script type="text/javascript">
// When DOM loads, init the page.
$(function() {
// Executes a callback detecting changes with a frequency of 1 second
$("#id_element_placeholder").observe_field(1, function( ) {
$.ajax({
type: "GET",
dataType: "json",
url: "/students/get/" + this.value,
success: function(data){
$('#last_name').attr('value', data.student.last_name);
$('#building').attr('value', data.student.building);
$('#room').attr('value', data.student.room);
}
});
});
});
</script>
Problem here is that I'm exposing lot of my code in javascript. Is there a better way to do it without exposing code in javascript?
Here is what my controller looks like:
def get
#student = Student.find(params[:id])
respond_to do |format|
format.html
format.json { render :json => #student }
end
end
Basically I get an id in a form and from there I have to get the corresponding object and update the fields on the page.
Assuming your design requires you to make AJAX calls to query student info by id, then you need to expose a URL for the call. If you don't want to expose a JSON data structure, you could return a chunk of HTML (instead of JSON), and replace the contents of the container of all of the controls you mention above.
Related
I have a "collection_select" of invoices and I want that when selecting an invoice I return the detail of it, then paint a table with the details in a view.
My problem is that I do not know how to call ajax the detail of a nested form.
you can get the invoice_id from the collection_select by jquery
$.ajax({
url: '/invoice/' + $('#dropDownId').val(),
type: 'GET',
success: function(res){
$('#your_invoice_display_area').html(res);
}
})
in your controller:
def show
#invoice = Invoice.find(params[:id])
respond_to do |format|
format.json {render json: #invoice, include: :invoice_items}
end
end
the json data will be something like this:
{"invoice":{"id":35,"bill_to":"Mr. X","address":"Address details","custom_date":"2017-07-05","subtotal":"5627.0","discount":null,"tax":null,"number":"INV_00035","created_at":"2017-04-05T16:52:04.071+06:00","updated_at":"2017-05-03T13:50:01.308+06:00","company":"Company Name","phone":"12345667","email":"email#gmail.com","billing_period":"","in_word":null,"total":"5627.0"}}
then, in the success function in your ajax call, you can use the json to paint a table.
first, make an empty table giving ids to the each field. Then use jquery to add the values.
success: function(res){
$("#bill_to").html(res["invoice"]["bill_to"]);
$("#address").html(res["invoice"]["address"]);
}
Hope that helps. Let me know if you need more help.
How do I escape a collection when I send it via to_json from the controller action directly client-side?
When I say the request is sent from the controller action and then directly to client-side (skips pre-processing) it looks like this:
AJAX request gets routed to a controller action
Controller Action directly sends the result via javascript to the requester, and on the client-side the requester's javascript would do the processing. something like: app/assets/javascripts/blogs.js
This is as opposed to the request being sent to a controller_action, then to a server-side view for pre-processing, then the results being sent to the requester. Looks like this:
AJAX request
Routed to a controller action
Sent to a view for pre-process. Something like: app/views/blogs/index.js.erb
Results get sent to the requester
Short Example:
def some_action
#blogs = Blog.all
respond_to do |format|
format.json {render json: #blogs} # currently dangerous, #blogs is not sanitized.
end
end
As an example: assume one of the #blogs records in that collection has this data input from a hacker:
#blogs.first.title
=> <script>alert('Site now hacked if this javascript runs!')</script>
When the #blogs get rendered in the browser: I want to escape the #blogs content so that the javascript will not get triggered from that hacked entry.
Longer Example:
The user makes a selection for a blogger in a select box.
An AJAX request gets sent which grabs all the blogs associated to that selected blogger.
The AJAX request then updates a second select box for blogs which will now list as options all the blogs that belong_to that selected blogger.
For the code: the controller action code above would be exactly the same. Below is the client-side javascript:
app/assets/javascripts/blogs.js
$("body").on('change', '[data-action="blogger_sel"]', function() {
var blogs_selection = $(this).closest('[data-parent-for="blogs_sel"]').find('[data-action="blogs_sel"]');
$.ajax({
url: "/blogs",
type: "GET",
data: {blogger_id: $(this).val()},
success: function (data) {
blogs_selection.children().remove();
$.each(data, function (index, item) {
blogs_selection.append('<option value=' + item.id + '>' + item.title + '</option>');
});
}
})
});
So up above: the part that I am concerned about is value.id and value.title. Those are things that could be dangerous if I do not escape them. I want to make sure it is escaped so that any dangerous input will be rendered harmless.
Below is a solution. Keep in mind that it is often times a good idea to sanitize data before it gets persisted into the database as well. Also: it is preferable to sanitize server-side before sending the response to the requester:
app/assets/javascripts/blogs.js
$("body").on('change', '[data-action="blogger_sel"]', function() {
var blog_sel = $(this).closest('[data-parent-for="blog_sel"]').find('[data-action="blog_sel"]');
$.ajax({
url: "/blogs",
type: "GET",
data: {blogger_id: $(this).val()},
success: function (data) {
blog_sel.children().remove();
$.each(data, function (index, item) {
blog_sel.append($('<option>', {
value: item.id,
text : item.title
}));
});
}
})
});
Do not append options the following way because it will execute dangerous hacks:
blogs_selection.append('<option value=' + value.id + '>' + value.title + '</option>');
You're dealing with an unsanitized HTML string like any other. You need to make it safe before inserting it on your page. All you really need to do is replace the < and > characters with < and >.
Using this controller as an example:
class SomethingController < ApplicationController
def jsontest
render json: { blogs: ["<script>alert(1);</script>"] }
end
end
jQuery can do this for you, if you don't mind the clunkiness:
$.ajax({
type: 'GET',
url: '/something/jsontest',
dataType: 'json',
success: function (data) {
console.log($('<div>').text(data['blogs'][0]).html());
}
})
> <script>alert(1);</script>
You could also look at using ActionView::Helpers::SanitizeHelper#sanitize on the controller side, which will clobber any HTML tags it finds. (Normally this is only available to views, so you could either make a view to render your JSON or include ActionView::Helpers::SanitizeHelper in your controller). Or do both!
I am trying to send data through a get request using ajax but the param doesn't seem to be getting sent. I have a page that shows a random item from the db. You get to this page from a link in the navbar. Once on this page there is a link that allows you to skip the current item to find another random item making sure the next item isn't the one the user was just viewing.
routes.rb
get 'pending', to: 'items#pending'
view
<%= link_to 'Skip', '', class: "btn btn-default btn-xs",
id: "skip-btn",
:'data-item-id' => #pending_item.id %>
controller
def pending
#previous_pending_item_id = params[:id] || 0
#pending_items = Item.pending.where("items.id != ?", #previous_pending_item_id)
#pending_item = #pending_items.offset(rand #pending_items.count).first
respond_to do |format|
format.js
format.html
end
end
I have respond to both html and js because I am using the action for the navbar link as well as the link on the page. The link on the page is the skip button which should bring up another random item.
sitewide.js.erb
$('#skip-btn').on('click', function () {
$.ajax({
data: {id: $(this).data("item-id")},
url: "/pending"
});
});
When I look in the server log is says Started GET "/pending"... but doesn't make any mention of a param being sent. What am I missing?
The reason I'm using ajax for this is because I don't want the param showing in the url.
For clarification I need the url when visiting this page to always be /pending with no params or additional :id identified in the url. This page should always show a random record form the db. The only reason I need to send a param is to make sure no record is every repeated consecutively even though they are random.
I think you need to prevent default link action:
$('#skip-btn').on('click', function (event) {
event.preventDefault();
...
});
While you can do it the way you're attempting, I think it's worth pointing out that sending data in a GET request is a bit of an antipattern. So why not doing it the "correct" way!
Change your routes.rb to:
get 'pending/:id', to: 'items#pending'
and change sitewide.js.erb to:
$('#skip-btn').on('click', function () {
$.ajax({
url: "/pending/" + $(this).data("item-id")
});
});
I'd like you to check for the format its sending the the query to your controller. And the type of format you want to receive at the front end.
$('#skip-btn').on('click', function (e) {
e.preventDefault();
$.ajax({
dataType: 'json', //This will ensure we are receiving a specific formatted response
data: {id: $(this).data("item-id")},
url: "/pending"
});
});
In your controller maybe you want to pass it back as a json object.
def pending
##previous_pending_item_id = params[:id] || 0
#No need to make it an instance variable
previous_pending_item_id = params[:id] || 0
#Same goes for pending_items. No need to make it a instance variable, unless you're using it somewhere else.
pending_items = Item.pending.where("items.id != ?", previous_pending_item_id)
#pending_item = pending_items.offset(rand pending_items.count).first
respond_to do |format|
format.js
format.html
format.json { render json: #pending_item.as_json }
end
end
So that you can take value from response and append it to your page.
Similarly if you are expecting a js or html response back, you should mention that in your ajax call. Let me know if it does help you resolve your issue.
Update:
Let's say in your page, it shows the data of #pending_item object in a div,
<div id="pending_item">...</div>
When you're making a ajax request to your controller you want div#pending_item to show the a new random pending_item.
$('#skip-btn').on('click', function (e) {
e.preventDefault();
$.ajax({
dataType: 'html', //we'll receive a html partial from server
data: {id: $(this).data("item-id")},
url: "/pending",
success: function(res){
//We'll set the html of our div to the response html we got
$("#pending_item").html(res);
}
});
});
In your controller you do something like:
format.html { render partial: 'pending_item', layout: false }
And in your partial file "_pendint_item.html.erb" you'll access your instance variable #pending_item and display data
This will send the response to server as html, and there you'll only set your div's html to this.
Update 2
Your partial might look like this, or just however you want to display your pending item. The thing to know is it will be accessing the same instance variable #pending_item you have defined in your controller's method, unless you pass locals to it.
<div class="pending_item">
<h3><%= #pending_item.name %></h3>
<p><%= #pending_item.description %></p>
</div>
I suggest you do a console.log(res) in the success callback of your ajax call to see what you're getting back from server.
Here's the situation: I have a view called "Dashboard" with a simple form and a "Draw Graphs" button. When I press this button I want to make two ajax requests that will return me the necessary data for my javascript to build two different graphs. The thing is that both requests will need to execute the exact same query on my database, and I want to avoid duplicating queries.
The way I'm currently doing this is by having my button fire a single ajax request that queries mysql for the necessary data, returning that data to my ajax success, and then passing that same data as params to two other ajax requests, which then use the data to generate the necessary structure for drawing the graphs.
It looks something like this:
Javascript:
$('#draw_graphs').click(function() {
$.ajax({
url: 'single_query',
dataType: 'json',
data: $('#myForm').serialize(),
method: 'get'
}).success(function(activeRecord) {
ajax_graph1(activeRecord);
ajax_graph2(activeRecord);
});
});
ajax_graph1 = function(activeRecord) {
$.ajax({
url: 'create_g1',
dataType: 'json',
data: {active_record: activeRecord},
method: 'post'
}).success(function(g1) {
create_g1(g1);
});
};
ajax_graph2 = function(activeRecord) {
$.ajax({
url: 'create_g2',
dataType: 'json',
data: {active_record: activeRecord},
method: 'post'
}).success(function(g2) {
create_g2(g2);
});
};
Rails:
def single_query
result = Data.where("etc... etc...")
respond_to do |format|
format.json { render json: result.to_json }
end
end
def create_g1
activerecord = params[:active_record]
graph1 = {}
activerecord.each do |ar|
#do whatever with graph1
end
respond_to do |format|
format.json { render json: graph1.to_json }
end
end
def create_g2
activerecord = params[:active_record]
graph2 = {}
activerecord.each do |ar|
#do whatever with graph2
end
respond_to do |format|
format.json { render json: graph1.to_json }
end
end
The problem I'm having is that apparently you can't simply send an active record from controller to javascript and back to controller again, the structure seems to get changed on the way. While single_query's result is of class ActiveRecord_Relation, when I pass it through the "javascript layer" it transforms into ActionController::Parameters class
Your reasoning makes sense: only want to hit the DB once. The issue is understanding how the "javascript layer" works with Rails. The AJAX call is getting an XHR Response object represented as JSON. This maps to a representation of your active_record in Rails but it's certainly not the same instance of an object in JS.
That being said, you should do what you need to do with the record on the Rails side and simply send the response to one AJAX call. In this case, have your $('#draw_graphs').click( AJAX call hit your controller in a corresponding def draw_graphs method. Have that method do the DB call, build each graph and pass both graphs back in a JSON hash (below). Then on the .success(function(graphs) parse the response and send the results to your 2 ajax_graph methods.
To build the JSON hash format.json { render json: { graph1.to_json, graph2.to_json } }
There are a few design optimizations here as well. You want to have a thin controller so consider only using the controller to sanitize/permit whatever params go into result = Data.where(...). Pass those params to a method in a class that does the query and maybe has a helper method to generate the graphs. It looks like you can even just do a case statement in that helper method based on which graph it is building because the create_g1 and create_g2 code looks similar. Likewise, you can refactor the code in the JS as well.
I seem to be having some trouble finding some good information on how to go about dynamically presenting forms on the same view. What I mean is, for example, A user clicks an "Add Sub-element" link on the show view for an "Element", and then is presented with a small form for adding a Sub-Element, and of course the same type of thing for editing existing Sub-Elements, on the same Show View using AJAX/JS/jQuery or something.
I was trying REST-in-place, but to no avail, as I have several nested objects (some within other nested objects), and that doesn't seem to work well with in-line-editing.
I believe my lack of information is coming from using incorrect search terminology, so any help would be appreciated to point me in the right direction.
Thanks in advance.
There are a lot of different ways to do it, one is using remote forms, like this railscasts example or the detailed answer on this question. Another would be to call a controller from jQuery and render (similar to my code here):
in javascript:
$(document.body).on('click', '#user-list-body', function (e) {
var url = '/get_user_list/
$.ajax({
url: url,
datatype: 'html',
type: 'GET',
success: function(data){
$('#user_table tbody').append(data);
}
});
});
in controller (note: drastically simplified version):
def get_user_list
#users = User.all
respond_to do |format|
format.html { render :layout => false, :partial => 'users/users_list'}
end
end