apply link_to_remote from a jQuery handler in Rails - javascript

I have a game with 5 boxes and when a box is clicked I want to send an ajax request to a controller that in effect is exactly like link_to_remote. I need to send a hash of informatoin to a definition in a controller but don't need anything to get sent back or even updated.
What the following jQuery/js mess does is render a function which either correct or incorrect. From those functions I want to be able to send an ajax request to some controller with a hash of information.
$('#tones a').click(function(e) {
if (playGame) {
id = this.id.replace('t', '')
if (correctCharacter[1] == id) {
correct();
} else {
incorrect();
}
addCharacters();
}
});
})
I know link_to_remote has the following syntax and this link is not really related at all:
# Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
link_to_remote "Delete this post", :update => "posts", :url => { :action => "destroy", :id => post.id }
If you could show me how to make this link work in the latter function. I just need one ajax link or request whatever is right that goes to a controller and sends a hash of information.
I want to put it in the following block. If it is correct then it will trigger and if it is not correct then it will trigger.
if (correctCharacter[1] == id) {
correct();
} else {
incorrect();
}
Here is js source from a link_to_remote I put on a page just to see the security stuff. It's not really related to where I want to post information but it is related to my specific application.
Test

This is what I came up with. It works except it isn't posting to the correct url.
$.ajax({
type: "POST",
url: "analytics/test",
data: 'correct=' + correct_or_incorrect + '&id=' + correctCharacter[3] + "'"
});

Related

Hide div after click of button with mysql

I want to hide a 'div' after a button is clicked. I don't want to use .remove() because when you refresh the app it comes back. I have the information about this div on the database and I wanna work with it.
I tried already creating an Ajax call to select the information that I'm looking at and then on the front-end I'm telling if it exist then delete it. But I feel like I'm missing something and I don't know why.
Frontend:
$('#deletePromo').on('click', function(res){
let success = function(res){
if (eventName && customerID){
$(`#promotion-container .promo${i}`).remove();
}
}
$.ajax({
type: 'POST',
url: '/api/promotions-done',
crossDomain: true,
//success: success,
dataType: 'json',
data: {
customerID : customerID,
eventName : eventName,
}
}).done(function(res){
console.log('res', res)
if (res != null){
$(`#promotion-container .promo${i}`).remove();
//$(`#promotion-container .promo${i}`).css('display', 'none')
}
})
})
})
Backend:
router.post('/promotions-done', function(req, res) {
let customerID = req.user.customer_id
let restaurantGroupId = req.user.restaurant_group_id
let eventName = req.body.eventName
db.task(t => {
return t.any(`SELECT * FROM promotions_redemption WHERE (customer_id = '${customerID}' AND event_name = '${eventName}' AND restaurant_group_id = ${restaurantGroupId})`).then(function(promotionsDone) {
return res.json({'promotionsDone': promotionsDone})
})
.catch((err) =>{
console.log(err)
})
})
})
What I'm trying to do here is saying if the customerID and eventName already exist on the table then remove div from the person. I don't have much experience working with backend so is there a way to tell the program to check the database for this information and if it exists then remove the div.
You probably have some HTML in a template file, or in the database that has the button there to start with. Since your AJAX code will only run when the button is clicked, you will need to either do 1 of 2 things:
Add an AJAX call on page load
Handle looking for the button and hide/show it in your templating language/platform (think asp.net, python django, php laravel etc) to avoid the AJAX request.
Since we don't know your platform, I will show you option 1
First, I would change the initial state of your HTML to NOT show the button by default. This would look something like this:
<div id="promotion-container">
<button class="promo" style="display: none" />
</div>
Otherwise you will have the button be shown for the amount of time the AJAX request takes.
Next, you will want to add this function call to the page. I have reversed the login in the done function to "show" the button or unhide it.
$(document).ready(function(){
let success = function(res){
if (eventName && customerID){
$(`#promotion-container .promo${i}`).remove();
}
}
$.ajax({
type: 'POST',
url: '/api/promotions-done',
crossDomain: true,
//success: success,
dataType: 'json',
data: {
customerID : customerID,
eventName : eventName,
}
}).done(function(res){
console.log('res', res)
if (res === null){
//$(`#promotion-container .promo${i}`).remove();
$(`#promotion-container .promo${i}`).css('display', 'block')
}
})
})
})
The easiest solution to your problem would be to use a client-side cookie. If you don't have a cookie package already, I'd recommend js-cookie.
On your html page:
<div id="promotion-container" hidden> //you want it hidden by default
//if it is shown by default there will be an annoying screen flicker when the pages loads
<script src="/path/to/js.cookie.js"></script>
On jQuery page:
$(document).ready( function() {
if (!Cookies.get('name1') || !Cookies.get('name2')) {
$('#promotion-container').show();
};
Cookies.set('name1', customerId, {expires:14} ); //must include expiration else the cookie will expire when the browser closes.
Cookies.set('name2', eventName, {expires:14} ); //you might need to make an eventId if eventName is too large
});
The second input for Cookies.set is the 'value' for the cookie. if 'value' = null, then Cookies.get('name') = null.
This is assuming you already have the means to set customerId and eventName for each user. Also you might need to modify where the Cookies are set based on when the customerId is created on the page.
EDIT:
There are 2 ways you can run a query the way you describe, both of which won't work they way you want unless you use a session cookie.
1) You have res.render inside of a query.
This would ensure the div is never shown to a user that has already clicked on it, but would significantly hurt your site's performance. The query would run every time the page is rendered, regardless of whether or not the client has a customerId. Your site would would be painfully slow with a large amount of traffic.
2) You run a POST request through client-side js with ajax and compare the customerId with your db; if a result is found, you remove the div.
This would function the way you want it to, and wouldn't hurt performance, but nothing is stopping a customer from using something like burp to intercept the POST request. They could change the data argument to whatever they want and make sure the div loads for them.
The only solution to these problems that I see would be to validate a user when they click on the div AND on the server with a session cookie. (for user validation I use passport and express-session).
I can show you how I set this up, but to make it specific to your needs I would need to know more about how your site is setup.
PS I misunderstood why you needed to hide the div, and using a client-side cookie would be a terrible idea in hindsight.

if else statement appears to run both

In my js erb file, both of my flash message is showing when only one should be. The ajax runs fine and my response is what it is supposed to be. However, for some reason, my flash message takes on the message in both the if and else statement. I put a debugger right before the if statement to test my response and it should've evaluated to true.
$.ajax({
url: '/purchases/options',
method: 'post',
dataType: 'json',
data: data
}).done(function(response){
if(response.duplicate === "true"){
<% flash.now[:alert]= 'This Addressi Is Already In Your Cart Or In Your Report Section.' %>
}
else{
<% flash.now[:notice]= 'Searchs result is added to your cart' %>
}
});
$("<%= j render(:partial => 'layouts/messages') %>").insertBefore('form')[0];
Since ERB is always going to get interpolated before it's sent to the browser, your browser is basically going to see this:
if(response.duplicate === "true") {
} else {
}
And your server is going to see this:
flash.now[:alert]= 'This Addressi Is Already In Your Cart Or In Your Report Section.'
flash.now[:notice]= 'Searchs result is added to your cart'
This basically means your server will always run both of those flash lines.
What you'll need to do, is move everything into the JS file without ERB's help, and manually set the flash text there.
For example, lets say you have the following markup on your page somewhere, that will be the "placeholder" for the flash text:
<div id="flash"></div>
Then in JavaScript, you'll just set the text manually:
$("<%= j render(:partial => 'layouts/messages') %>").insertBefore('form')[0];
$.ajax({
url: '/purchases/options',
method: 'post',
dataType: 'json',
data: data
}).done(function(response){
if (response.duplicate === "true") {
$("#flash").text("This Addressi Is Already In Your Cart Or In Your Report Section.");
$("#flash").addClass("alert");
} else{
$("#flash").text("Searchs result is added to your cart");
$("#flash").addClass("notice");
}
});
Any ruby code (obviously inside ruby tag) inside javascript will always be executed, irrespective of the if condition.
Rather you can set the flash message in controller or if you want to show the error/success message in current page, then use JS code instead like
$(your-element).text("your-text")

Rails: How to escape_javascript with client-side AJAX request

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!

Rails - why isn't ajax sending params to GET request

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.

Can't get Ruby on Rails 4 Ajax working with Checkbox -Update Firing: but isn't changing checkbox or database

I'm attempting to use Ajax with Ruby on Rails 4 to watch when the checkbox changes and submit the form and save the checkbox change to the database. Even with the debugger I'm not seeing any errors. Any thoughts?
I recently changed the path to edit because I was getting a 404. I did this to it matches a route.
I just added the success function to the ajax call and it appears to be firing.
This is what I have currently:
This is product_controller
def update
if #product.update(safe_params)
redirect_to [:edit, #product], flash: { notice: t('shoppe.products.update_notice') }
else
render action: 'edit'
end
end
This is form in haml
= form_for product, :remote => true do |f|
= f.check_box :active
= f.label :active, t('shoppe.products.active'), :remote => true
This is js:
$('#product_active').bind('change', function() {
console.log('changed');
var action = $(this).parents('form').attr('action')+"/edit";
var method = "GET";
var checked = $(this).attr('checked');
var data = $(this).attr('value');
data ^= 1;
$.ajax({
method: method,
url: action,
data: checked,
success: function() {
alert("AJAX Fired");
}
})
debugger;
});
This is from rake routes
products GET /products(.:format) shoppe/products#index
POST /products(.:format) shoppe/products#create
new_product GET /products/new(.:format) shoppe/products#new
edit_product GET /products/:id/edit(.:format) shoppe/products#edit
product GET /products/:id(.:format) shoppe/products#show
PATCH /products/:id(.:format) shoppe/products#update
PUT /products/:id(.:format) shoppe/products#update
DELETE /products/:id(.:format)
UPDATE: I ended up putting a hidden submit button with the form and changing my js to and adding in a class to the form to allow me to hit all ajax with one set of code:
$('.ajax').bind('change', function() {
$(this).parents('form').submit();
});
You need to hit the update endpoint of your routes not the edit. The edit route is usually to load the edit view.
You'll also want to return a json encoded response in the update action and handle the success jquery callback and from there you would redirect in the client side or do whatever you want to do to update the UI. A redirect would be only useful from within your rails code if you were actually submitting a form and not doing ajax.

Categories

Resources